@cubis/foundry 0.3.14 → 0.3.16
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 +42 -1
- package/bin/cubis.js +396 -40
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -20,6 +20,23 @@ Compatibility binaries are still shipped for migration:
|
|
|
20
20
|
- `cubiskill`
|
|
21
21
|
- `cubis`
|
|
22
22
|
|
|
23
|
+
## Quick Setup (Simple)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1) Install CLI
|
|
27
|
+
npm install -g @cubis/foundry
|
|
28
|
+
|
|
29
|
+
# 2) Set Postman key once (recommended: env mode)
|
|
30
|
+
export POSTMAN_API_KEY="<your-postman-api-key>"
|
|
31
|
+
|
|
32
|
+
# 3) Install workflow bundle for your platform
|
|
33
|
+
cbx workflows install --platform codex --bundle agent-environment-setup --postman --yes
|
|
34
|
+
|
|
35
|
+
# 4) Optional: install for other platforms too
|
|
36
|
+
cbx workflows install --platform antigravity --bundle agent-environment-setup --postman --yes
|
|
37
|
+
cbx workflows install --platform copilot --bundle agent-environment-setup --postman --yes
|
|
38
|
+
```
|
|
39
|
+
|
|
23
40
|
## Command Model
|
|
24
41
|
|
|
25
42
|
`workflows` is now the primary command group.
|
|
@@ -36,13 +53,23 @@ cbx workflows install --platform antigravity --dry-run
|
|
|
36
53
|
cbx workflows install --platform antigravity --terminal-integration --terminal-verifier codex
|
|
37
54
|
cbx workflows install --platform codex --postman
|
|
38
55
|
cbx workflows install --platform codex --postman --postman-workspace-id null
|
|
56
|
+
cbx workflows install --platform codex --postman --postman-api-key "<key>"
|
|
57
|
+
cbx workflows install --platform antigravity --postman
|
|
58
|
+
cbx workflows install --platform copilot --postman
|
|
39
59
|
```
|
|
40
60
|
|
|
41
61
|
Install bootstrap behavior:
|
|
42
62
|
- `cbx workflows install` now also bootstraps `ENGINEERING_RULES.md` and `TECH.md` (creates when missing; keeps existing files unless explicitly regenerated).
|
|
43
|
-
- Optional `--postman` bootstrap creates `postman_setting.json` and installs/configures the Postman skill.
|
|
63
|
+
- Optional `--postman` bootstrap creates `postman_setting.json` and installs/configures the Postman skill/MCP for Codex, Antigravity, and Copilot.
|
|
44
64
|
- Use `cbx rules init --platform <platform> --overwrite` to force-regenerate both files.
|
|
45
65
|
|
|
66
|
+
Postman setup behavior:
|
|
67
|
+
- `postman_setting.json` is generated in project root (or `~/.cbx/postman_setting.json` with `--scope global`).
|
|
68
|
+
- Env-first auth is supported: when `POSTMAN_API_KEY` is set, generated settings keep `apiKey: null` and MCP config uses `Bearer ${POSTMAN_API_KEY}`.
|
|
69
|
+
- Inline auth is supported with `--postman-api-key <key>`.
|
|
70
|
+
- `--postman-workspace-id null` writes JSON `null` for `defaultWorkspaceId`.
|
|
71
|
+
- In project scope, `postman_setting.json` is auto-added to `.gitignore` (no duplicate entries).
|
|
72
|
+
|
|
46
73
|
`rules` manages strict engineering policy and a generated codebase tech map:
|
|
47
74
|
|
|
48
75
|
```bash
|
|
@@ -60,6 +87,14 @@ What `cbx rules init` does:
|
|
|
60
87
|
- Generates `TECH.md` at workspace root by scanning the current codebase.
|
|
61
88
|
- Preserves user content outside managed markers.
|
|
62
89
|
|
|
90
|
+
`TECH.md` scanner coverage (deterministic, no AI calls):
|
|
91
|
+
- Language/file signals from workspace scan.
|
|
92
|
+
- JS/TS package signals from `package.json` (including nested/monorepo package files).
|
|
93
|
+
- Flutter/Dart package signals from `pubspec.yaml`.
|
|
94
|
+
- Go module signals from `go.mod`.
|
|
95
|
+
- Python package signals from `requirements*.txt` and `pyproject.toml`.
|
|
96
|
+
- Rust crate signals from `Cargo.toml`.
|
|
97
|
+
|
|
63
98
|
### Deprecated Alias
|
|
64
99
|
|
|
65
100
|
`cbx skills ...` still works for one minor cycle and prints a deprecation notice.
|
|
@@ -317,6 +352,12 @@ Run attribute validation (strict, fails on warnings):
|
|
|
317
352
|
npm run test:attributes:strict
|
|
318
353
|
```
|
|
319
354
|
|
|
355
|
+
Run TECH.md scanner integration tests:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
npm run test:tech-md
|
|
359
|
+
```
|
|
360
|
+
|
|
320
361
|
Run full workflow smoke test:
|
|
321
362
|
|
|
322
363
|
```bash
|
package/bin/cubis.js
CHANGED
|
@@ -172,6 +172,54 @@ const TECH_LANGUAGE_BY_EXTENSION = new Map([
|
|
|
172
172
|
[".sh", "Shell"],
|
|
173
173
|
[".ps1", "PowerShell"]
|
|
174
174
|
]);
|
|
175
|
+
const TECH_PACKAGE_PREVIEW_LIMIT = 40;
|
|
176
|
+
const TECH_JS_FRAMEWORK_SIGNALS = [
|
|
177
|
+
["next", "Next.js"],
|
|
178
|
+
["react", "React"],
|
|
179
|
+
["vue", "Vue"],
|
|
180
|
+
["nuxt", "Nuxt"],
|
|
181
|
+
["svelte", "Svelte"],
|
|
182
|
+
["@nestjs/core", "NestJS"],
|
|
183
|
+
["express", "Express"],
|
|
184
|
+
["fastify", "Fastify"],
|
|
185
|
+
["hono", "Hono"],
|
|
186
|
+
["tailwindcss", "Tailwind CSS"],
|
|
187
|
+
["prisma", "Prisma"],
|
|
188
|
+
["drizzle-orm", "Drizzle ORM"],
|
|
189
|
+
["mongoose", "Mongoose"],
|
|
190
|
+
["typeorm", "TypeORM"],
|
|
191
|
+
["@playwright/test", "Playwright"],
|
|
192
|
+
["vitest", "Vitest"],
|
|
193
|
+
["jest", "Jest"],
|
|
194
|
+
["cypress", "Cypress"]
|
|
195
|
+
];
|
|
196
|
+
const TECH_DART_FRAMEWORK_SIGNALS = [
|
|
197
|
+
["flutter_riverpod", "Riverpod"],
|
|
198
|
+
["riverpod", "Riverpod"],
|
|
199
|
+
["go_router", "go_router"],
|
|
200
|
+
["dio", "Dio"],
|
|
201
|
+
["freezed", "Freezed"],
|
|
202
|
+
["bloc", "BLoC"]
|
|
203
|
+
];
|
|
204
|
+
const TECH_GO_FRAMEWORK_SIGNALS = [
|
|
205
|
+
["github.com/gofiber/fiber/v2", "Go Fiber"],
|
|
206
|
+
["github.com/gin-gonic/gin", "Gin"],
|
|
207
|
+
["github.com/labstack/echo/v4", "Echo"],
|
|
208
|
+
["github.com/go-chi/chi/v5", "Chi"]
|
|
209
|
+
];
|
|
210
|
+
const TECH_PYTHON_FRAMEWORK_SIGNALS = [
|
|
211
|
+
["fastapi", "FastAPI"],
|
|
212
|
+
["django", "Django"],
|
|
213
|
+
["flask", "Flask"],
|
|
214
|
+
["pydantic", "Pydantic"],
|
|
215
|
+
["sqlalchemy", "SQLAlchemy"]
|
|
216
|
+
];
|
|
217
|
+
const TECH_RUST_FRAMEWORK_SIGNALS = [
|
|
218
|
+
["axum", "Axum"],
|
|
219
|
+
["actix-web", "Actix Web"],
|
|
220
|
+
["rocket", "Rocket"],
|
|
221
|
+
["tokio", "Tokio"]
|
|
222
|
+
];
|
|
175
223
|
|
|
176
224
|
function platformInstallsCustomAgents(platformId) {
|
|
177
225
|
const profile = WORKFLOW_PROFILES[platformId];
|
|
@@ -585,6 +633,198 @@ async function upsertEngineeringRulesBlock({
|
|
|
585
633
|
};
|
|
586
634
|
}
|
|
587
635
|
|
|
636
|
+
function normalizeTechPackageName(value) {
|
|
637
|
+
if (value === undefined || value === null) return null;
|
|
638
|
+
const normalized = String(value).trim().replace(/^['"]|['"]$/g, "").toLowerCase();
|
|
639
|
+
return normalized || null;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function parseTomlSections(content) {
|
|
643
|
+
const sections = new Map();
|
|
644
|
+
let currentSection = "";
|
|
645
|
+
sections.set(currentSection, []);
|
|
646
|
+
|
|
647
|
+
for (const line of content.split(/\r?\n/)) {
|
|
648
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*$/);
|
|
649
|
+
if (sectionMatch) {
|
|
650
|
+
currentSection = sectionMatch[1].trim();
|
|
651
|
+
if (!sections.has(currentSection)) sections.set(currentSection, []);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
sections.get(currentSection).push(line);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return sections;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function parsePubspecDependencyNames(content) {
|
|
661
|
+
const packages = new Set();
|
|
662
|
+
let currentSection = null;
|
|
663
|
+
|
|
664
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
665
|
+
const line = rawLine.replace(/\t/g, " ");
|
|
666
|
+
const trimmed = line.trim();
|
|
667
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
668
|
+
|
|
669
|
+
if (!line.startsWith(" ")) {
|
|
670
|
+
const sectionMatch = trimmed.match(/^([a-zA-Z0-9_]+):\s*$/);
|
|
671
|
+
if (!sectionMatch) {
|
|
672
|
+
currentSection = null;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
currentSection = sectionMatch[1];
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (currentSection !== "dependencies" && currentSection !== "dev_dependencies") continue;
|
|
680
|
+
const depMatch = trimmed.match(/^([a-zA-Z0-9_]+):/);
|
|
681
|
+
if (!depMatch) continue;
|
|
682
|
+
const packageName = normalizeTechPackageName(depMatch[1]);
|
|
683
|
+
if (!packageName || packageName === "flutter" || packageName === "sdk") continue;
|
|
684
|
+
packages.add(packageName);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return packages;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function parseGoModuleNames(content) {
|
|
691
|
+
const modules = new Set();
|
|
692
|
+
let inRequireBlock = false;
|
|
693
|
+
|
|
694
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
695
|
+
const trimmed = rawLine.trim();
|
|
696
|
+
if (!trimmed || trimmed.startsWith("//")) continue;
|
|
697
|
+
|
|
698
|
+
if (trimmed.startsWith("require (")) {
|
|
699
|
+
inRequireBlock = true;
|
|
700
|
+
continue;
|
|
701
|
+
}
|
|
702
|
+
if (inRequireBlock && trimmed === ")") {
|
|
703
|
+
inRequireBlock = false;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (inRequireBlock) {
|
|
708
|
+
const moduleName = normalizeTechPackageName(trimmed.split(/\s+/)[0]);
|
|
709
|
+
if (moduleName) modules.add(moduleName);
|
|
710
|
+
continue;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (trimmed.startsWith("require ")) {
|
|
714
|
+
const moduleName = normalizeTechPackageName(trimmed.slice("require ".length).trim().split(/\s+/)[0]);
|
|
715
|
+
if (moduleName) modules.add(moduleName);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return modules;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function parseRequirementsPackageNames(content) {
|
|
723
|
+
const packages = new Set();
|
|
724
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
725
|
+
const withoutComment = rawLine.split("#")[0].trim();
|
|
726
|
+
if (!withoutComment) continue;
|
|
727
|
+
if (withoutComment.startsWith("-")) continue;
|
|
728
|
+
|
|
729
|
+
const cleaned = withoutComment.split(";")[0].trim();
|
|
730
|
+
const match = cleaned.match(/^([A-Za-z0-9_.-]+)/);
|
|
731
|
+
if (!match) continue;
|
|
732
|
+
const packageName = normalizeTechPackageName(match[1]);
|
|
733
|
+
if (packageName) packages.add(packageName);
|
|
734
|
+
}
|
|
735
|
+
return packages;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function parsePyprojectPackageNames(content) {
|
|
739
|
+
const packages = new Set();
|
|
740
|
+
const sections = parseTomlSections(content);
|
|
741
|
+
|
|
742
|
+
const projectSection = sections.get("project");
|
|
743
|
+
if (projectSection) {
|
|
744
|
+
const projectBody = projectSection.join("\n");
|
|
745
|
+
const dependenciesArrayMatch = projectBody.match(/dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
746
|
+
if (dependenciesArrayMatch) {
|
|
747
|
+
const entries = dependenciesArrayMatch[1].match(/"([^"]+)"|'([^']+)'/g) || [];
|
|
748
|
+
for (const entry of entries) {
|
|
749
|
+
const normalizedEntry = normalizeTechPackageName(entry);
|
|
750
|
+
if (!normalizedEntry) continue;
|
|
751
|
+
const packageName = normalizeTechPackageName(normalizedEntry.split(/[<>=!~\s\[]/)[0]);
|
|
752
|
+
if (packageName) packages.add(packageName);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
for (const line of projectSection) {
|
|
757
|
+
const trimmed = line.trim();
|
|
758
|
+
const optionalDepsMatch = trimmed.match(
|
|
759
|
+
/^([A-Za-z0-9_.-]+)\s*=\s*\[\s*("([^"]+)"|'([^']+)')/
|
|
760
|
+
);
|
|
761
|
+
if (!optionalDepsMatch) continue;
|
|
762
|
+
const entry = optionalDepsMatch[3] || optionalDepsMatch[4];
|
|
763
|
+
const packageName = normalizeTechPackageName(String(entry).split(/[<>=!~\s\[]/)[0]);
|
|
764
|
+
if (packageName) packages.add(packageName);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
for (const [sectionName, lines] of sections.entries()) {
|
|
769
|
+
const isPoetryDependencySection =
|
|
770
|
+
sectionName === "tool.poetry.dependencies" ||
|
|
771
|
+
/^tool\.poetry\.group\.[^.]+\.dependencies$/.test(sectionName);
|
|
772
|
+
if (!isPoetryDependencySection) continue;
|
|
773
|
+
|
|
774
|
+
for (const line of lines) {
|
|
775
|
+
const trimmed = line.trim();
|
|
776
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
777
|
+
const depMatch = trimmed.match(/^([A-Za-z0-9_.-]+)\s*=/);
|
|
778
|
+
if (!depMatch) continue;
|
|
779
|
+
const packageName = normalizeTechPackageName(depMatch[1]);
|
|
780
|
+
if (!packageName || packageName === "python") continue;
|
|
781
|
+
packages.add(packageName);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return packages;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function parseCargoCrateNames(content) {
|
|
789
|
+
const crates = new Set();
|
|
790
|
+
const sections = parseTomlSections(content);
|
|
791
|
+
|
|
792
|
+
for (const [sectionName, lines] of sections.entries()) {
|
|
793
|
+
const isDependencySection =
|
|
794
|
+
sectionName === "dependencies" ||
|
|
795
|
+
sectionName === "dev-dependencies" ||
|
|
796
|
+
sectionName === "build-dependencies" ||
|
|
797
|
+
sectionName === "workspace.dependencies" ||
|
|
798
|
+
/\.dependencies$/.test(sectionName) ||
|
|
799
|
+
/\.dev-dependencies$/.test(sectionName) ||
|
|
800
|
+
/\.build-dependencies$/.test(sectionName);
|
|
801
|
+
if (!isDependencySection) continue;
|
|
802
|
+
|
|
803
|
+
for (const line of lines) {
|
|
804
|
+
const trimmed = line.trim();
|
|
805
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
806
|
+
const depMatch = trimmed.match(/^([A-Za-z0-9_-]+)\s*=/);
|
|
807
|
+
if (!depMatch) continue;
|
|
808
|
+
const crateName = normalizeTechPackageName(depMatch[1]);
|
|
809
|
+
if (crateName) crates.add(crateName);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return crates;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function addFrameworkSignalsFromPackages({ packages, frameworks, signals }) {
|
|
817
|
+
for (const [signal, frameworkLabel] of signals) {
|
|
818
|
+
if (packages.has(signal.toLowerCase())) {
|
|
819
|
+
frameworks.add(frameworkLabel);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
function toSortedArray(values) {
|
|
825
|
+
return [...values].sort((a, b) => a.localeCompare(b));
|
|
826
|
+
}
|
|
827
|
+
|
|
588
828
|
async function collectTechSnapshot(rootDir) {
|
|
589
829
|
const discoveredFiles = [];
|
|
590
830
|
const queue = [rootDir];
|
|
@@ -614,6 +854,12 @@ async function collectTechSnapshot(rootDir) {
|
|
|
614
854
|
|
|
615
855
|
const languageCounts = new Map();
|
|
616
856
|
const topDirs = new Set();
|
|
857
|
+
const packageJsonFiles = [];
|
|
858
|
+
const pubspecFiles = [];
|
|
859
|
+
const goModFiles = [];
|
|
860
|
+
const pyprojectFiles = [];
|
|
861
|
+
const requirementsFiles = [];
|
|
862
|
+
const cargoTomlFiles = [];
|
|
617
863
|
|
|
618
864
|
for (const fullPath of discoveredFiles) {
|
|
619
865
|
const rel = toPosixPath(path.relative(rootDir, fullPath));
|
|
@@ -622,15 +868,31 @@ async function collectTechSnapshot(rootDir) {
|
|
|
622
868
|
|
|
623
869
|
const extension = path.extname(fullPath).toLowerCase();
|
|
624
870
|
const language = TECH_LANGUAGE_BY_EXTENSION.get(extension);
|
|
625
|
-
if (
|
|
626
|
-
|
|
871
|
+
if (language) {
|
|
872
|
+
languageCounts.set(language, (languageCounts.get(language) || 0) + 1);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
const baseName = path.basename(fullPath).toLowerCase();
|
|
876
|
+
if (baseName === "package.json") packageJsonFiles.push(fullPath);
|
|
877
|
+
if (baseName === "pubspec.yaml") pubspecFiles.push(fullPath);
|
|
878
|
+
if (baseName === "go.mod") goModFiles.push(fullPath);
|
|
879
|
+
if (baseName === "pyproject.toml") pyprojectFiles.push(fullPath);
|
|
880
|
+
if (baseName === "cargo.toml") cargoTomlFiles.push(fullPath);
|
|
881
|
+
if (baseName === "requirements.txt" || /^requirements(?:[-_.].+)?\.txt$/.test(baseName)) {
|
|
882
|
+
requirementsFiles.push(fullPath);
|
|
883
|
+
}
|
|
627
884
|
}
|
|
628
885
|
|
|
629
886
|
const fileExists = (name) => existsSync(path.join(rootDir, name));
|
|
630
|
-
const
|
|
887
|
+
const rootPackageJsonPath = path.join(rootDir, "package.json");
|
|
631
888
|
const packageScripts = new Map();
|
|
632
889
|
const frameworks = new Set();
|
|
633
890
|
const lockfiles = [];
|
|
891
|
+
const javascriptPackages = new Set();
|
|
892
|
+
const dartPackages = new Set();
|
|
893
|
+
const goModules = new Set();
|
|
894
|
+
const pythonPackages = new Set();
|
|
895
|
+
const rustCrates = new Set();
|
|
634
896
|
|
|
635
897
|
if (fileExists("bun.lock") || fileExists("bun.lockb")) lockfiles.push("bun");
|
|
636
898
|
if (fileExists("pnpm-lock.yaml")) lockfiles.push("pnpm");
|
|
@@ -641,54 +903,118 @@ async function collectTechSnapshot(rootDir) {
|
|
|
641
903
|
if (fileExists("go.sum")) lockfiles.push("go");
|
|
642
904
|
if (fileExists("pubspec.lock")) lockfiles.push("pub");
|
|
643
905
|
|
|
644
|
-
if (
|
|
645
|
-
if (
|
|
646
|
-
if (
|
|
647
|
-
if (
|
|
906
|
+
if (pubspecFiles.length > 0) frameworks.add("Flutter");
|
|
907
|
+
if (goModFiles.length > 0) frameworks.add("Go Modules");
|
|
908
|
+
if (cargoTomlFiles.length > 0) frameworks.add("Rust Cargo");
|
|
909
|
+
if (requirementsFiles.length > 0 || pyprojectFiles.length > 0) frameworks.add("Python");
|
|
648
910
|
|
|
649
|
-
|
|
911
|
+
for (const packageJsonFile of packageJsonFiles) {
|
|
650
912
|
try {
|
|
651
|
-
const parsed = JSON.parse(await readFile(
|
|
652
|
-
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
653
|
-
for (const [name, command] of Object.entries(scripts)) {
|
|
654
|
-
if (typeof command !== "string") continue;
|
|
655
|
-
packageScripts.set(name, command);
|
|
656
|
-
}
|
|
657
|
-
|
|
913
|
+
const parsed = JSON.parse(await readFile(packageJsonFile, "utf8"));
|
|
658
914
|
const deps = {
|
|
659
915
|
...(parsed.dependencies || {}),
|
|
660
916
|
...(parsed.devDependencies || {}),
|
|
661
|
-
...(parsed.peerDependencies || {})
|
|
917
|
+
...(parsed.peerDependencies || {}),
|
|
918
|
+
...(parsed.optionalDependencies || {})
|
|
662
919
|
};
|
|
663
|
-
const
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
[
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
["tailwindcss", "Tailwind CSS"],
|
|
675
|
-
["prisma", "Prisma"],
|
|
676
|
-
["drizzle-orm", "Drizzle ORM"],
|
|
677
|
-
["mongoose", "Mongoose"],
|
|
678
|
-
["typeorm", "TypeORM"],
|
|
679
|
-
["@playwright/test", "Playwright"],
|
|
680
|
-
["vitest", "Vitest"],
|
|
681
|
-
["jest", "Jest"],
|
|
682
|
-
["cypress", "Cypress"]
|
|
683
|
-
];
|
|
684
|
-
for (const [signal, label] of frameworkSignals) {
|
|
685
|
-
if (depNames.has(signal)) frameworks.add(label);
|
|
920
|
+
for (const depName of Object.keys(deps)) {
|
|
921
|
+
const normalized = normalizeTechPackageName(depName);
|
|
922
|
+
if (normalized) javascriptPackages.add(normalized);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (path.resolve(packageJsonFile) === path.resolve(rootPackageJsonPath)) {
|
|
926
|
+
const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
|
|
927
|
+
for (const [name, command] of Object.entries(scripts)) {
|
|
928
|
+
if (typeof command !== "string") continue;
|
|
929
|
+
packageScripts.set(name, command);
|
|
930
|
+
}
|
|
686
931
|
}
|
|
687
932
|
} catch {
|
|
688
933
|
// ignore malformed package.json
|
|
689
934
|
}
|
|
690
935
|
}
|
|
691
936
|
|
|
937
|
+
for (const pubspecFile of pubspecFiles) {
|
|
938
|
+
try {
|
|
939
|
+
const content = await readFile(pubspecFile, "utf8");
|
|
940
|
+
for (const packageName of parsePubspecDependencyNames(content)) {
|
|
941
|
+
dartPackages.add(packageName);
|
|
942
|
+
}
|
|
943
|
+
} catch {
|
|
944
|
+
// ignore malformed pubspec.yaml
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
for (const goModFile of goModFiles) {
|
|
949
|
+
try {
|
|
950
|
+
const content = await readFile(goModFile, "utf8");
|
|
951
|
+
for (const moduleName of parseGoModuleNames(content)) {
|
|
952
|
+
goModules.add(moduleName);
|
|
953
|
+
}
|
|
954
|
+
} catch {
|
|
955
|
+
// ignore unreadable go.mod
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
for (const pyprojectFile of pyprojectFiles) {
|
|
960
|
+
try {
|
|
961
|
+
const content = await readFile(pyprojectFile, "utf8");
|
|
962
|
+
for (const packageName of parsePyprojectPackageNames(content)) {
|
|
963
|
+
pythonPackages.add(packageName);
|
|
964
|
+
}
|
|
965
|
+
} catch {
|
|
966
|
+
// ignore malformed pyproject.toml
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
for (const requirementsFile of requirementsFiles) {
|
|
971
|
+
try {
|
|
972
|
+
const content = await readFile(requirementsFile, "utf8");
|
|
973
|
+
for (const packageName of parseRequirementsPackageNames(content)) {
|
|
974
|
+
pythonPackages.add(packageName);
|
|
975
|
+
}
|
|
976
|
+
} catch {
|
|
977
|
+
// ignore unreadable requirements file
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
for (const cargoTomlFile of cargoTomlFiles) {
|
|
982
|
+
try {
|
|
983
|
+
const content = await readFile(cargoTomlFile, "utf8");
|
|
984
|
+
for (const crateName of parseCargoCrateNames(content)) {
|
|
985
|
+
rustCrates.add(crateName);
|
|
986
|
+
}
|
|
987
|
+
} catch {
|
|
988
|
+
// ignore malformed Cargo.toml
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
addFrameworkSignalsFromPackages({
|
|
993
|
+
packages: javascriptPackages,
|
|
994
|
+
frameworks,
|
|
995
|
+
signals: TECH_JS_FRAMEWORK_SIGNALS
|
|
996
|
+
});
|
|
997
|
+
addFrameworkSignalsFromPackages({
|
|
998
|
+
packages: dartPackages,
|
|
999
|
+
frameworks,
|
|
1000
|
+
signals: TECH_DART_FRAMEWORK_SIGNALS
|
|
1001
|
+
});
|
|
1002
|
+
addFrameworkSignalsFromPackages({
|
|
1003
|
+
packages: goModules,
|
|
1004
|
+
frameworks,
|
|
1005
|
+
signals: TECH_GO_FRAMEWORK_SIGNALS
|
|
1006
|
+
});
|
|
1007
|
+
addFrameworkSignalsFromPackages({
|
|
1008
|
+
packages: pythonPackages,
|
|
1009
|
+
frameworks,
|
|
1010
|
+
signals: TECH_PYTHON_FRAMEWORK_SIGNALS
|
|
1011
|
+
});
|
|
1012
|
+
addFrameworkSignalsFromPackages({
|
|
1013
|
+
packages: rustCrates,
|
|
1014
|
+
frameworks,
|
|
1015
|
+
signals: TECH_RUST_FRAMEWORK_SIGNALS
|
|
1016
|
+
});
|
|
1017
|
+
|
|
692
1018
|
const sortedLanguages = [...languageCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
693
1019
|
const sortedFrameworks = [...frameworks].sort((a, b) => a.localeCompare(b));
|
|
694
1020
|
const sortedTopDirs = [...topDirs].sort((a, b) => a.localeCompare(b)).slice(0, 12);
|
|
@@ -717,10 +1043,33 @@ async function collectTechSnapshot(rootDir) {
|
|
|
717
1043
|
frameworks: sortedFrameworks,
|
|
718
1044
|
lockfiles: sortedLockfiles,
|
|
719
1045
|
topDirs: sortedTopDirs,
|
|
720
|
-
keyScripts
|
|
1046
|
+
keyScripts,
|
|
1047
|
+
packageSignals: {
|
|
1048
|
+
javascript: toSortedArray(javascriptPackages),
|
|
1049
|
+
dart: toSortedArray(dartPackages),
|
|
1050
|
+
go: toSortedArray(goModules),
|
|
1051
|
+
python: toSortedArray(pythonPackages),
|
|
1052
|
+
rust: toSortedArray(rustCrates)
|
|
1053
|
+
}
|
|
721
1054
|
};
|
|
722
1055
|
}
|
|
723
1056
|
|
|
1057
|
+
function appendTechPackageSection(lines, heading, packages) {
|
|
1058
|
+
lines.push(`### ${heading}`);
|
|
1059
|
+
if (!packages || packages.length === 0) {
|
|
1060
|
+
lines.push("- None detected.");
|
|
1061
|
+
} else {
|
|
1062
|
+
const preview = packages.slice(0, TECH_PACKAGE_PREVIEW_LIMIT);
|
|
1063
|
+
for (const packageName of preview) {
|
|
1064
|
+
lines.push(`- \`${packageName}\``);
|
|
1065
|
+
}
|
|
1066
|
+
if (packages.length > TECH_PACKAGE_PREVIEW_LIMIT) {
|
|
1067
|
+
lines.push(`- ... (+${packages.length - TECH_PACKAGE_PREVIEW_LIMIT} more)`);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
lines.push("");
|
|
1071
|
+
}
|
|
1072
|
+
|
|
724
1073
|
function buildTechMd(snapshot) {
|
|
725
1074
|
const lines = [];
|
|
726
1075
|
lines.push("# TECH.md");
|
|
@@ -750,6 +1099,13 @@ function buildTechMd(snapshot) {
|
|
|
750
1099
|
}
|
|
751
1100
|
lines.push("");
|
|
752
1101
|
|
|
1102
|
+
lines.push("## Package Signals");
|
|
1103
|
+
appendTechPackageSection(lines, "JavaScript / TypeScript (package.json)", snapshot.packageSignals.javascript);
|
|
1104
|
+
appendTechPackageSection(lines, "Dart / Flutter (pubspec.yaml)", snapshot.packageSignals.dart);
|
|
1105
|
+
appendTechPackageSection(lines, "Go Modules (go.mod)", snapshot.packageSignals.go);
|
|
1106
|
+
appendTechPackageSection(lines, "Python Packages (requirements / pyproject)", snapshot.packageSignals.python);
|
|
1107
|
+
appendTechPackageSection(lines, "Rust Crates (Cargo.toml)", snapshot.packageSignals.rust);
|
|
1108
|
+
|
|
753
1109
|
lines.push("## Tooling and Lockfiles");
|
|
754
1110
|
if (snapshot.lockfiles.length === 0) {
|
|
755
1111
|
lines.push("- No lockfiles detected.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cubis/foundry",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
4
4
|
"description": "Cubis Foundry CLI for workflow-first AI agent environments",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"check": "node bin/cubis.js --help",
|
|
19
19
|
"test:attributes": "node scripts/validate-platform-attributes.mjs",
|
|
20
20
|
"test:attributes:strict": "node scripts/validate-platform-attributes.mjs --strict",
|
|
21
|
+
"test:tech-md": "node scripts/test-tech-md-scanner.mjs",
|
|
21
22
|
"test:smoke": "bash scripts/smoke-workflows.sh",
|
|
22
23
|
"test:all": "npm run test:attributes && npm run test:smoke",
|
|
23
24
|
"test:all:strict": "npm run test:attributes:strict && npm run test:smoke",
|