guardian-framework 0.1.142 → 0.1.144
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/dist/cli.js +130 -19
- package/dist/exports.js +125 -18
- package/package.json +1 -1
- package/templates/pi/architecture/implementation-roadmap.md +6 -2
- package/templates/pi/extensions/domain-explorer.ts +14 -3
- package/templates/pi/extensions/project-scaffolder.ts +8 -3
- package/templates/pi/preflight_report.json +2 -2
- package/templates/pi/scripts/check_language_consistency.sh +122 -0
- package/templates/pi/scripts/validate-architecture.sh +20 -2
package/dist/cli.js
CHANGED
|
@@ -3206,7 +3206,7 @@ __export(exports_package, {
|
|
|
3206
3206
|
bin: () => bin,
|
|
3207
3207
|
author: () => author
|
|
3208
3208
|
});
|
|
3209
|
-
var name = "guardian-framework", version = "0.1.
|
|
3209
|
+
var name = "guardian-framework", version = "0.1.144", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
3210
3210
|
var init_package = __esm(() => {
|
|
3211
3211
|
exports = {
|
|
3212
3212
|
".": {
|
|
@@ -11625,6 +11625,117 @@ import * as path3 from "path";
|
|
|
11625
11625
|
import * as crypto from "crypto";
|
|
11626
11626
|
import * as fs from "fs";
|
|
11627
11627
|
import * as path2 from "path";
|
|
11628
|
+
|
|
11629
|
+
// src/lib/language-stubs.ts
|
|
11630
|
+
var FENCE = {
|
|
11631
|
+
typescript: "typescript",
|
|
11632
|
+
nextjs: "typescript",
|
|
11633
|
+
angular: "typescript",
|
|
11634
|
+
rust: "rust",
|
|
11635
|
+
python: "python",
|
|
11636
|
+
go: "go",
|
|
11637
|
+
java: "java"
|
|
11638
|
+
};
|
|
11639
|
+
function normalizeStubLanguage(language) {
|
|
11640
|
+
const l2 = (language ?? "").trim().toLowerCase();
|
|
11641
|
+
if (l2 === "nextjs" || l2 === "angular" || l2 === "typescript")
|
|
11642
|
+
return "typescript";
|
|
11643
|
+
if (l2 === "rust" || l2 === "python" || l2 === "go" || l2 === "java")
|
|
11644
|
+
return l2;
|
|
11645
|
+
return "neutral";
|
|
11646
|
+
}
|
|
11647
|
+
function renderRepositoryInterface(language, aggregateName) {
|
|
11648
|
+
const lang = normalizeStubLanguage(language);
|
|
11649
|
+
const fence = FENCE[lang] ?? "text";
|
|
11650
|
+
const id = `${aggregateName}Id`;
|
|
11651
|
+
if (lang === "rust") {
|
|
11652
|
+
return [
|
|
11653
|
+
`\`\`\`${fence}`,
|
|
11654
|
+
`pub trait ${aggregateName}Repository: Send + Sync {`,
|
|
11655
|
+
` async fn find_by_id(&self, id: &${id}) -> Result<Option<${aggregateName}>, DomainError>;`,
|
|
11656
|
+
` async fn save(&self, ${lowerFirst(aggregateName)}: &${aggregateName}) -> Result<(), DomainError>;`,
|
|
11657
|
+
` async fn delete(&self, id: &${id}) -> Result<(), DomainError>;`,
|
|
11658
|
+
"}",
|
|
11659
|
+
"```"
|
|
11660
|
+
].join(`
|
|
11661
|
+
`);
|
|
11662
|
+
}
|
|
11663
|
+
if (lang === "python") {
|
|
11664
|
+
return [
|
|
11665
|
+
`\`\`\`${fence}`,
|
|
11666
|
+
"from abc import ABC, abstractmethod",
|
|
11667
|
+
"",
|
|
11668
|
+
"",
|
|
11669
|
+
`class ${aggregateName}Repository(ABC):`,
|
|
11670
|
+
` """Persistence port for the ${aggregateName} aggregate (implemented in infrastructure)."""`,
|
|
11671
|
+
"",
|
|
11672
|
+
" @abstractmethod",
|
|
11673
|
+
` async def find_by_id(self, id_: ${id}) -> ${aggregateName} | None: ...`,
|
|
11674
|
+
"",
|
|
11675
|
+
" @abstractmethod",
|
|
11676
|
+
` async def save(self, ${snake(aggregateName)}: ${aggregateName}) -> None: ...`,
|
|
11677
|
+
"",
|
|
11678
|
+
" @abstractmethod",
|
|
11679
|
+
` async def delete(self, id_: ${id}) -> None: ...`,
|
|
11680
|
+
"```"
|
|
11681
|
+
].join(`
|
|
11682
|
+
`);
|
|
11683
|
+
}
|
|
11684
|
+
if (lang === "java") {
|
|
11685
|
+
return [
|
|
11686
|
+
`\`\`\`${fence}`,
|
|
11687
|
+
`public interface ${aggregateName}Repository {`,
|
|
11688
|
+
` Optional<${aggregateName}> findById(${id} id);`,
|
|
11689
|
+
` void save(${aggregateName} ${lowerFirst(aggregateName)});`,
|
|
11690
|
+
` void delete(${id} id);`,
|
|
11691
|
+
"}",
|
|
11692
|
+
"```"
|
|
11693
|
+
].join(`
|
|
11694
|
+
`);
|
|
11695
|
+
}
|
|
11696
|
+
if (lang === "go") {
|
|
11697
|
+
return [
|
|
11698
|
+
`\`\`\`${fence}`,
|
|
11699
|
+
`// ${aggregateName}Repository is the persistence port for the ${aggregateName} aggregate.`,
|
|
11700
|
+
`type ${aggregateName}Repository interface {`,
|
|
11701
|
+
` FindByID(ctx context.Context, id ${id}) (*${aggregateName}, error)`,
|
|
11702
|
+
` Save(ctx context.Context, ${lowerFirst(aggregateName)} *${aggregateName}) error`,
|
|
11703
|
+
` Delete(ctx context.Context, id ${id}) error`,
|
|
11704
|
+
"}",
|
|
11705
|
+
"```"
|
|
11706
|
+
].join(`
|
|
11707
|
+
`);
|
|
11708
|
+
}
|
|
11709
|
+
if (lang === "typescript") {
|
|
11710
|
+
return [
|
|
11711
|
+
`\`\`\`${fence}`,
|
|
11712
|
+
`export interface ${aggregateName}Repository {`,
|
|
11713
|
+
` findById(id: ${id}): Promise<${aggregateName} | null>;`,
|
|
11714
|
+
` save(${lowerFirst(aggregateName)}: ${aggregateName}): Promise<void>;`,
|
|
11715
|
+
` delete(id: ${id}): Promise<void>;`,
|
|
11716
|
+
"}",
|
|
11717
|
+
"```"
|
|
11718
|
+
].join(`
|
|
11719
|
+
`);
|
|
11720
|
+
}
|
|
11721
|
+
return [
|
|
11722
|
+
`\`\`\`${fence}`,
|
|
11723
|
+
`${aggregateName}Repository (persistence port)`,
|
|
11724
|
+
` findById(id: ${id}) -> ${aggregateName} | null`,
|
|
11725
|
+
` save(${aggregateName}) -> void`,
|
|
11726
|
+
` delete(id: ${id}) -> void`,
|
|
11727
|
+
"```"
|
|
11728
|
+
].join(`
|
|
11729
|
+
`);
|
|
11730
|
+
}
|
|
11731
|
+
function lowerFirst(s) {
|
|
11732
|
+
return s.length > 0 ? s.charAt(0).toLowerCase() + s.slice(1) : s;
|
|
11733
|
+
}
|
|
11734
|
+
function snake(s) {
|
|
11735
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
11736
|
+
}
|
|
11737
|
+
|
|
11738
|
+
// src/lib/domain-explorer.ts
|
|
11628
11739
|
var __dirname = "/Users/arman/project/guardian-framework/src/lib", __filename = "/Users/arman/project/guardian-framework/src/lib/domain-explorer.ts";
|
|
11629
11740
|
var MAX_BUSINESS_CONTEXT_LENGTH = 5000;
|
|
11630
11741
|
function sanitizeBusinessContext(context) {
|
|
@@ -12793,23 +12904,11 @@ ${bc.description}
|
|
|
12793
12904
|
content += `
|
|
12794
12905
|
`;
|
|
12795
12906
|
}
|
|
12796
|
-
const arVarName = ar.name.charAt(0).toLowerCase() + ar.name.slice(1);
|
|
12797
12907
|
content += `**Repository Interface:**
|
|
12798
12908
|
`;
|
|
12799
12909
|
content += `
|
|
12800
12910
|
`;
|
|
12801
|
-
content +=
|
|
12802
|
-
content += `pub trait ${ar.name}Repository: Send + Sync {
|
|
12803
|
-
`;
|
|
12804
|
-
content += ` async fn find_by_id(&self, id: &${ar.name}Id) -> Result<Option<${ar.name}>, DomainError>;
|
|
12805
|
-
`;
|
|
12806
|
-
content += ` async fn save(&self, ${arVarName}: &${ar.name}) -> Result<(), DomainError>;
|
|
12807
|
-
`;
|
|
12808
|
-
content += ` async fn delete(&self, id: &${ar.name}Id) -> Result<(), DomainError>;
|
|
12809
|
-
`;
|
|
12810
|
-
content += `}
|
|
12811
|
-
`;
|
|
12812
|
-
content += "```";
|
|
12911
|
+
content += renderRepositoryInterface(lang, ar.name);
|
|
12813
12912
|
content += `
|
|
12814
12913
|
`;
|
|
12815
12914
|
if (ownedEntities.length > 0 || ownedVOs.length > 0 || invariants.length > 0) {
|
|
@@ -14583,7 +14682,8 @@ function generateProjectStructure(targetDir, options) {
|
|
|
14583
14682
|
return { modules, layers, files: files2 };
|
|
14584
14683
|
}
|
|
14585
14684
|
function runProjectGenerator(targetDir, archDir, options) {
|
|
14586
|
-
const
|
|
14685
|
+
const withContexts = options.withContexts === true;
|
|
14686
|
+
const modules = options.dryRun ? withContexts ? ["billing", "notifications", "shared"] : ["shared"] : withContexts ? discoverModules(archDir) : ["shared"];
|
|
14587
14687
|
const resolvedModules = modules.length > 0 ? modules : ["shared"];
|
|
14588
14688
|
const layers = resolveLayers(archDir, options.language, options.defaults);
|
|
14589
14689
|
return generateProjectStructure(targetDir, {
|
|
@@ -14603,7 +14703,7 @@ function existingProjectDetected(targetDir) {
|
|
|
14603
14703
|
return fs15.existsSync(srcDir);
|
|
14604
14704
|
}
|
|
14605
14705
|
async function runProjectCreate(targetDir, options) {
|
|
14606
|
-
const { language, buildTool, groupId, repoTool, validators, dryRun, force } = options;
|
|
14706
|
+
const { language, buildTool, groupId, repoTool, validators, dryRun, force, withContexts } = options;
|
|
14607
14707
|
if (!SUPPORTED_LANGUAGES.includes(language)) {
|
|
14608
14708
|
showError(`Unsupported language: ${language}. Supported: ${SUPPORTED_LANGUAGES.join(", ")}`);
|
|
14609
14709
|
return;
|
|
@@ -14639,7 +14739,8 @@ async function runProjectCreate(targetDir, options) {
|
|
|
14639
14739
|
groupId,
|
|
14640
14740
|
buildTool,
|
|
14641
14741
|
dryRun,
|
|
14642
|
-
defaults
|
|
14742
|
+
defaults,
|
|
14743
|
+
withContexts
|
|
14643
14744
|
});
|
|
14644
14745
|
if (dryRun) {
|
|
14645
14746
|
console.log(" Modules:");
|
|
@@ -14653,8 +14754,14 @@ async function runProjectCreate(targetDir, options) {
|
|
|
14653
14754
|
console.log(` Files to create: ${structurePlan.files.length}
|
|
14654
14755
|
`);
|
|
14655
14756
|
} else {
|
|
14656
|
-
|
|
14757
|
+
const scope = withContexts ? `${structurePlan.modules.length} bounded contexts` : "minimal bootstrap: shared kernel";
|
|
14758
|
+
console.log(` \u2705 ${structurePlan.files.length} files created (${scope} \xD7 ${structurePlan.layers.length} layers)
|
|
14657
14759
|
`);
|
|
14760
|
+
if (!withContexts) {
|
|
14761
|
+
console.log(" \u2139\uFE0F Minimal bootstrap: bounded-context folders are created per roadmap phase.");
|
|
14762
|
+
console.log(` Pre-create every context tree with --withContexts (not recommended).
|
|
14763
|
+
`);
|
|
14764
|
+
}
|
|
14658
14765
|
}
|
|
14659
14766
|
console.log(dryRun ? "\uD83D\uDCCB [DRY-RUN] Generating build configuration..." : "\uD83D\uDCC4 Generating build configuration...");
|
|
14660
14767
|
const buildPlan = generateBuildConfig(targetDir, {
|
|
@@ -15571,6 +15678,7 @@ async function runCli() {
|
|
|
15571
15678
|
workflows: { type: "string" },
|
|
15572
15679
|
nonInteractive: { type: "boolean" },
|
|
15573
15680
|
dryRun: { type: "boolean" },
|
|
15681
|
+
withContexts: { type: "boolean" },
|
|
15574
15682
|
groupId: { type: "string" },
|
|
15575
15683
|
force: { type: "boolean" },
|
|
15576
15684
|
regenerate: { type: "boolean" },
|
|
@@ -15629,6 +15737,8 @@ Project options:
|
|
|
15629
15737
|
--validators <list> Validators for CI pipeline
|
|
15630
15738
|
--dryRun Show plan without writing files
|
|
15631
15739
|
--force Override existing project guard
|
|
15740
|
+
--withContexts Pre-create every bounded-context tree (default: minimal
|
|
15741
|
+
bootstrap \u2014 shared kernel only; contexts come per phase)
|
|
15632
15742
|
--workflows <list> Workflows (comma-separated)
|
|
15633
15743
|
--nonInteractive Use defaults/flags, skip prompts
|
|
15634
15744
|
|
|
@@ -15737,7 +15847,8 @@ async function runCommand(command, args) {
|
|
|
15737
15847
|
repoTool: resolvedRepoTool,
|
|
15738
15848
|
validators: args.values.validators?.split(",") || ["ci", "tests"],
|
|
15739
15849
|
dryRun: args.values.dryRun,
|
|
15740
|
-
force: args.values.force
|
|
15850
|
+
force: args.values.force,
|
|
15851
|
+
withContexts: args.values.withContexts
|
|
15741
15852
|
});
|
|
15742
15853
|
} else {
|
|
15743
15854
|
console.error("Usage: guardian project create [options]");
|
package/dist/exports.js
CHANGED
|
@@ -1991,7 +1991,7 @@ __export(exports_package, {
|
|
|
1991
1991
|
bin: () => bin,
|
|
1992
1992
|
author: () => author
|
|
1993
1993
|
});
|
|
1994
|
-
var name = "guardian-framework", version = "0.1.
|
|
1994
|
+
var name = "guardian-framework", version = "0.1.144", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
1995
1995
|
var init_package = __esm(() => {
|
|
1996
1996
|
exports = {
|
|
1997
1997
|
".": {
|
|
@@ -11948,6 +11948,117 @@ import * as path12 from "node:path";
|
|
|
11948
11948
|
import * as crypto4 from "node:crypto";
|
|
11949
11949
|
import * as fs12 from "node:fs";
|
|
11950
11950
|
import * as path11 from "node:path";
|
|
11951
|
+
|
|
11952
|
+
// src/lib/language-stubs.ts
|
|
11953
|
+
var FENCE = {
|
|
11954
|
+
typescript: "typescript",
|
|
11955
|
+
nextjs: "typescript",
|
|
11956
|
+
angular: "typescript",
|
|
11957
|
+
rust: "rust",
|
|
11958
|
+
python: "python",
|
|
11959
|
+
go: "go",
|
|
11960
|
+
java: "java"
|
|
11961
|
+
};
|
|
11962
|
+
function normalizeStubLanguage(language) {
|
|
11963
|
+
const l2 = (language ?? "").trim().toLowerCase();
|
|
11964
|
+
if (l2 === "nextjs" || l2 === "angular" || l2 === "typescript")
|
|
11965
|
+
return "typescript";
|
|
11966
|
+
if (l2 === "rust" || l2 === "python" || l2 === "go" || l2 === "java")
|
|
11967
|
+
return l2;
|
|
11968
|
+
return "neutral";
|
|
11969
|
+
}
|
|
11970
|
+
function renderRepositoryInterface(language, aggregateName) {
|
|
11971
|
+
const lang = normalizeStubLanguage(language);
|
|
11972
|
+
const fence = FENCE[lang] ?? "text";
|
|
11973
|
+
const id = `${aggregateName}Id`;
|
|
11974
|
+
if (lang === "rust") {
|
|
11975
|
+
return [
|
|
11976
|
+
`\`\`\`${fence}`,
|
|
11977
|
+
`pub trait ${aggregateName}Repository: Send + Sync {`,
|
|
11978
|
+
` async fn find_by_id(&self, id: &${id}) -> Result<Option<${aggregateName}>, DomainError>;`,
|
|
11979
|
+
` async fn save(&self, ${lowerFirst(aggregateName)}: &${aggregateName}) -> Result<(), DomainError>;`,
|
|
11980
|
+
` async fn delete(&self, id: &${id}) -> Result<(), DomainError>;`,
|
|
11981
|
+
"}",
|
|
11982
|
+
"```"
|
|
11983
|
+
].join(`
|
|
11984
|
+
`);
|
|
11985
|
+
}
|
|
11986
|
+
if (lang === "python") {
|
|
11987
|
+
return [
|
|
11988
|
+
`\`\`\`${fence}`,
|
|
11989
|
+
"from abc import ABC, abstractmethod",
|
|
11990
|
+
"",
|
|
11991
|
+
"",
|
|
11992
|
+
`class ${aggregateName}Repository(ABC):`,
|
|
11993
|
+
` """Persistence port for the ${aggregateName} aggregate (implemented in infrastructure)."""`,
|
|
11994
|
+
"",
|
|
11995
|
+
" @abstractmethod",
|
|
11996
|
+
` async def find_by_id(self, id_: ${id}) -> ${aggregateName} | None: ...`,
|
|
11997
|
+
"",
|
|
11998
|
+
" @abstractmethod",
|
|
11999
|
+
` async def save(self, ${snake(aggregateName)}: ${aggregateName}) -> None: ...`,
|
|
12000
|
+
"",
|
|
12001
|
+
" @abstractmethod",
|
|
12002
|
+
` async def delete(self, id_: ${id}) -> None: ...`,
|
|
12003
|
+
"```"
|
|
12004
|
+
].join(`
|
|
12005
|
+
`);
|
|
12006
|
+
}
|
|
12007
|
+
if (lang === "java") {
|
|
12008
|
+
return [
|
|
12009
|
+
`\`\`\`${fence}`,
|
|
12010
|
+
`public interface ${aggregateName}Repository {`,
|
|
12011
|
+
` Optional<${aggregateName}> findById(${id} id);`,
|
|
12012
|
+
` void save(${aggregateName} ${lowerFirst(aggregateName)});`,
|
|
12013
|
+
` void delete(${id} id);`,
|
|
12014
|
+
"}",
|
|
12015
|
+
"```"
|
|
12016
|
+
].join(`
|
|
12017
|
+
`);
|
|
12018
|
+
}
|
|
12019
|
+
if (lang === "go") {
|
|
12020
|
+
return [
|
|
12021
|
+
`\`\`\`${fence}`,
|
|
12022
|
+
`// ${aggregateName}Repository is the persistence port for the ${aggregateName} aggregate.`,
|
|
12023
|
+
`type ${aggregateName}Repository interface {`,
|
|
12024
|
+
` FindByID(ctx context.Context, id ${id}) (*${aggregateName}, error)`,
|
|
12025
|
+
` Save(ctx context.Context, ${lowerFirst(aggregateName)} *${aggregateName}) error`,
|
|
12026
|
+
` Delete(ctx context.Context, id ${id}) error`,
|
|
12027
|
+
"}",
|
|
12028
|
+
"```"
|
|
12029
|
+
].join(`
|
|
12030
|
+
`);
|
|
12031
|
+
}
|
|
12032
|
+
if (lang === "typescript") {
|
|
12033
|
+
return [
|
|
12034
|
+
`\`\`\`${fence}`,
|
|
12035
|
+
`export interface ${aggregateName}Repository {`,
|
|
12036
|
+
` findById(id: ${id}): Promise<${aggregateName} | null>;`,
|
|
12037
|
+
` save(${lowerFirst(aggregateName)}: ${aggregateName}): Promise<void>;`,
|
|
12038
|
+
` delete(id: ${id}): Promise<void>;`,
|
|
12039
|
+
"}",
|
|
12040
|
+
"```"
|
|
12041
|
+
].join(`
|
|
12042
|
+
`);
|
|
12043
|
+
}
|
|
12044
|
+
return [
|
|
12045
|
+
`\`\`\`${fence}`,
|
|
12046
|
+
`${aggregateName}Repository (persistence port)`,
|
|
12047
|
+
` findById(id: ${id}) -> ${aggregateName} | null`,
|
|
12048
|
+
` save(${aggregateName}) -> void`,
|
|
12049
|
+
` delete(id: ${id}) -> void`,
|
|
12050
|
+
"```"
|
|
12051
|
+
].join(`
|
|
12052
|
+
`);
|
|
12053
|
+
}
|
|
12054
|
+
function lowerFirst(s) {
|
|
12055
|
+
return s.length > 0 ? s.charAt(0).toLowerCase() + s.slice(1) : s;
|
|
12056
|
+
}
|
|
12057
|
+
function snake(s) {
|
|
12058
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
|
|
12059
|
+
}
|
|
12060
|
+
|
|
12061
|
+
// src/lib/domain-explorer.ts
|
|
11951
12062
|
var __dirname = "/Users/arman/project/guardian-framework/src/lib", __filename = "/Users/arman/project/guardian-framework/src/lib/domain-explorer.ts";
|
|
11952
12063
|
var MAX_BUSINESS_CONTEXT_LENGTH = 5000;
|
|
11953
12064
|
function sanitizeBusinessContext(context) {
|
|
@@ -13116,23 +13227,11 @@ ${bc.description}
|
|
|
13116
13227
|
content += `
|
|
13117
13228
|
`;
|
|
13118
13229
|
}
|
|
13119
|
-
const arVarName = ar.name.charAt(0).toLowerCase() + ar.name.slice(1);
|
|
13120
13230
|
content += `**Repository Interface:**
|
|
13121
13231
|
`;
|
|
13122
13232
|
content += `
|
|
13123
13233
|
`;
|
|
13124
|
-
content +=
|
|
13125
|
-
content += `pub trait ${ar.name}Repository: Send + Sync {
|
|
13126
|
-
`;
|
|
13127
|
-
content += ` async fn find_by_id(&self, id: &${ar.name}Id) -> Result<Option<${ar.name}>, DomainError>;
|
|
13128
|
-
`;
|
|
13129
|
-
content += ` async fn save(&self, ${arVarName}: &${ar.name}) -> Result<(), DomainError>;
|
|
13130
|
-
`;
|
|
13131
|
-
content += ` async fn delete(&self, id: &${ar.name}Id) -> Result<(), DomainError>;
|
|
13132
|
-
`;
|
|
13133
|
-
content += `}
|
|
13134
|
-
`;
|
|
13135
|
-
content += "```";
|
|
13234
|
+
content += renderRepositoryInterface(lang, ar.name);
|
|
13136
13235
|
content += `
|
|
13137
13236
|
`;
|
|
13138
13237
|
if (ownedEntities.length > 0 || ownedVOs.length > 0 || invariants.length > 0) {
|
|
@@ -14143,7 +14242,8 @@ function generateProjectStructure(targetDir, options) {
|
|
|
14143
14242
|
return { modules, layers, files: files2 };
|
|
14144
14243
|
}
|
|
14145
14244
|
function runProjectGenerator(targetDir, archDir, options) {
|
|
14146
|
-
const
|
|
14245
|
+
const withContexts = options.withContexts === true;
|
|
14246
|
+
const modules = options.dryRun ? withContexts ? ["billing", "notifications", "shared"] : ["shared"] : withContexts ? discoverModules(archDir) : ["shared"];
|
|
14147
14247
|
const resolvedModules = modules.length > 0 ? modules : ["shared"];
|
|
14148
14248
|
const layers = resolveLayers(archDir, options.language, options.defaults);
|
|
14149
14249
|
return generateProjectStructure(targetDir, {
|
|
@@ -14163,7 +14263,7 @@ function existingProjectDetected(targetDir) {
|
|
|
14163
14263
|
return fs17.existsSync(srcDir);
|
|
14164
14264
|
}
|
|
14165
14265
|
async function runProjectCreate(targetDir, options) {
|
|
14166
|
-
const { language, buildTool, groupId, repoTool, validators, dryRun, force } = options;
|
|
14266
|
+
const { language, buildTool, groupId, repoTool, validators, dryRun, force, withContexts } = options;
|
|
14167
14267
|
if (!SUPPORTED_LANGUAGES.includes(language)) {
|
|
14168
14268
|
showError(`Unsupported language: ${language}. Supported: ${SUPPORTED_LANGUAGES.join(", ")}`);
|
|
14169
14269
|
return;
|
|
@@ -14199,7 +14299,8 @@ async function runProjectCreate(targetDir, options) {
|
|
|
14199
14299
|
groupId,
|
|
14200
14300
|
buildTool,
|
|
14201
14301
|
dryRun,
|
|
14202
|
-
defaults
|
|
14302
|
+
defaults,
|
|
14303
|
+
withContexts
|
|
14203
14304
|
});
|
|
14204
14305
|
if (dryRun) {
|
|
14205
14306
|
console.log(" Modules:");
|
|
@@ -14213,8 +14314,14 @@ async function runProjectCreate(targetDir, options) {
|
|
|
14213
14314
|
console.log(` Files to create: ${structurePlan.files.length}
|
|
14214
14315
|
`);
|
|
14215
14316
|
} else {
|
|
14216
|
-
|
|
14317
|
+
const scope = withContexts ? `${structurePlan.modules.length} bounded contexts` : "minimal bootstrap: shared kernel";
|
|
14318
|
+
console.log(` ✅ ${structurePlan.files.length} files created (${scope} × ${structurePlan.layers.length} layers)
|
|
14217
14319
|
`);
|
|
14320
|
+
if (!withContexts) {
|
|
14321
|
+
console.log(" ℹ️ Minimal bootstrap: bounded-context folders are created per roadmap phase.");
|
|
14322
|
+
console.log(` Pre-create every context tree with --withContexts (not recommended).
|
|
14323
|
+
`);
|
|
14324
|
+
}
|
|
14218
14325
|
}
|
|
14219
14326
|
console.log(dryRun ? "\uD83D\uDCCB [DRY-RUN] Generating build configuration..." : "\uD83D\uDCC4 Generating build configuration...");
|
|
14220
14327
|
const buildPlan = generateBuildConfig(targetDir, {
|
package/package.json
CHANGED
|
@@ -13,8 +13,12 @@ contexts into delivery phases with explicit dependencies, database migrations, a
|
|
|
13
13
|
acceptance criteria.
|
|
14
14
|
|
|
15
15
|
**Format conventions:**
|
|
16
|
-
-
|
|
17
|
-
|
|
16
|
+
- `## Phase 0: Project Bootstrap` is MINIMUM project setup (build/deps, shared kernel layout,
|
|
17
|
+
config, error envelope, migration tooling, CI + validators, test harness, entrypoint). It is
|
|
18
|
+
NOT a bounded context: no `### Modules` rows and no context folders (context folders are
|
|
19
|
+
created in each context's own build phase)
|
|
20
|
+
- Each `## Phase N` (N≥1) section defines one delivery phase
|
|
21
|
+
- `### Modules` table lists ONLY the bounded contexts built in this phase (contexts start Phase 1)
|
|
18
22
|
- **Invariant:** each bounded context appears in exactly ONE phase's `### Modules` table
|
|
19
23
|
(its build phase). Modules a phase merely uses are referenced under `### Dependencies`,
|
|
20
24
|
never re-listed as Modules (no repeats, no "All")
|
|
@@ -1746,18 +1746,29 @@ export default function (pi: ExtensionAPI) {
|
|
|
1746
1746
|
"each having: Goal, Modules table, Dependencies, Database Migrations, Acceptance Criteria.",
|
|
1747
1747
|
"",
|
|
1748
1748
|
"HARD RULES:",
|
|
1749
|
+
"- Phase 0 is ALWAYS `## Phase 0: Project Bootstrap` — MINIMUM project setup, NOT a",
|
|
1750
|
+
" bounded context. It has no `### Modules` rows (write 'Bootstrap only — no bounded",
|
|
1751
|
+
" contexts' or omit the table). Deliverables: language toolchain + dependency manifest,",
|
|
1752
|
+
" top-level package layout with the shared kernel, config/env handling, error envelope +",
|
|
1753
|
+
" logging baseline, DB migration TOOLING (no module tables), CI + architecture validators,",
|
|
1754
|
+
" test harness, entrypoint with /healthz + /readyz. Reference `/project create` in the",
|
|
1755
|
+
" Goal line. Acceptance: project builds and boots; lint/test/validators green on an empty",
|
|
1756
|
+
" suite. Do NOT create bounded-context folders — each context's folders are created in its",
|
|
1757
|
+
" own build phase.",
|
|
1749
1758
|
"- Each bounded context appears in EXACTLY ONE phase's `### Modules` table — the phase where",
|
|
1750
1759
|
" it is built (the earliest phase whose dependencies are already satisfied). Never re-list a",
|
|
1751
1760
|
" module in a later phase: no repeats, no 'All', no 'Hardening applies to all modules' row.",
|
|
1752
1761
|
"- Modules a phase merely USES go under `### Dependencies`, naming what they provide",
|
|
1753
1762
|
" (ports/types/endpoints). Keep those references — they are useful and must not be dropped.",
|
|
1754
|
-
"- Total rows across
|
|
1763
|
+
"- Total `### Modules` rows across phases 1..N MUST equal the number of module docs",
|
|
1764
|
+
" (Phase 0 Bootstrap contributes 0 rows).",
|
|
1755
1765
|
"- Keep the `| Module | Deliverables | Architecture Doc |` columns; Architecture Doc links",
|
|
1756
1766
|
" are the per-phase reference that implementation agents follow.",
|
|
1757
1767
|
"",
|
|
1758
1768
|
"Organize phases by dependency chain:",
|
|
1759
|
-
"- Phase 0:
|
|
1760
|
-
"- Phase 1:
|
|
1769
|
+
"- Phase 0: Project Bootstrap — minimal setup only (no bounded contexts, no context folders)",
|
|
1770
|
+
"- Phase 1: Foundation — contexts with no upstream dependencies (their folders appear here)",
|
|
1771
|
+
"- Phase 2+: contexts whose dependencies were built in earlier phases, and so on",
|
|
1761
1772
|
"- Do not create a phase just to group later touches of an already-built module.",
|
|
1762
1773
|
"",
|
|
1763
1774
|
"Also add: Dependency Graph (phase arrows + which modules cross phase boundaries),",
|
|
@@ -154,6 +154,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
154
154
|
const validators = String(parsed.validators || "ci,tests");
|
|
155
155
|
const dryRun = parsed.dryRun === true || parsed["dry-run"] === true || parsed.d === true;
|
|
156
156
|
const force = parsed.force === true || parsed.f === true;
|
|
157
|
+
const withContexts = parsed.withContexts === true || parsed["with-contexts"] === true;
|
|
157
158
|
|
|
158
159
|
// Validate language
|
|
159
160
|
if (!lang || !SUPPORTED_LANGUAGES.includes(lang as typeof SUPPORTED_LANGUAGES[number])) {
|
|
@@ -194,6 +195,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
194
195
|
};
|
|
195
196
|
if (dryRun) shellArgs.dryRun = true;
|
|
196
197
|
if (force) shellArgs.force = true;
|
|
198
|
+
if (withContexts) shellArgs.withContexts = true;
|
|
197
199
|
|
|
198
200
|
const fullCmd = cmd(`${guardianCmd} project create`, shellArgs);
|
|
199
201
|
|
|
@@ -232,7 +234,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
232
234
|
"",
|
|
233
235
|
dryRun
|
|
234
236
|
? "Run without --dryRun to create the project."
|
|
235
|
-
:
|
|
237
|
+
: withContexts
|
|
238
|
+
? "Project scaffolded (all context trees pre-created). Next: /architect to plan epics."
|
|
239
|
+
: "Minimal project bootstrap complete (shared kernel only; context folders are created per roadmap phase). Next: /architect to plan epics.",
|
|
236
240
|
]
|
|
237
241
|
.filter(Boolean)
|
|
238
242
|
.join("\n");
|
|
@@ -259,9 +263,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
259
263
|
"Available /project subcommands:",
|
|
260
264
|
"",
|
|
261
265
|
" /project create --lang <language> [options]",
|
|
262
|
-
"
|
|
266
|
+
" Minimal project bootstrap from architecture decisions (shared kernel + build/CI).",
|
|
267
|
+
" Bounded-context folders are created per roadmap phase, not upfront.",
|
|
263
268
|
" Required: --lang (typescript, rust, python, go, java)",
|
|
264
|
-
" Options: --buildTool, --groupId, --validators, --dryRun, --force",
|
|
269
|
+
" Options: --buildTool, --groupId, --validators, --dryRun, --force, --withContexts",
|
|
265
270
|
"",
|
|
266
271
|
" /project status",
|
|
267
272
|
" Show current scaffolding status",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2026-
|
|
2
|
+
"timestamp": "2026-11-07T13:11:23Z",
|
|
3
3
|
"mode": "all",
|
|
4
4
|
"stages_run": [],
|
|
5
5
|
"summary": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"failed": 1,
|
|
9
9
|
"skipped": 12
|
|
10
10
|
},
|
|
11
|
-
"duration_seconds":
|
|
11
|
+
"duration_seconds": 0,
|
|
12
12
|
"results": [
|
|
13
13
|
{
|
|
14
14
|
"name": "check_mr_traceability.sh",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ============================================================================
|
|
3
|
+
# check_language_consistency.sh — architecture docs must use the project language
|
|
4
|
+
# ============================================================================
|
|
5
|
+
# Catches two failure modes:
|
|
6
|
+
# 1. Scaffold emitting stubs in the wrong language (historically Rust in every
|
|
7
|
+
# project, regardless of manifest language).
|
|
8
|
+
# 2. An enrichment/codegen agent copying a foreign-language sample.
|
|
9
|
+
#
|
|
10
|
+
# Fences for "infrastructure" languages that are legitimately language-agnostic
|
|
11
|
+
# (json, yaml, toml, bash, sql, mermaid, text, dockerfile, http, diff, ini) are
|
|
12
|
+
# always allowed; markdown is allowed too.
|
|
13
|
+
#
|
|
14
|
+
# Usage: bash .pi/scripts/check_language_consistency.sh [--strict]
|
|
15
|
+
# --strict also treat other framework languages (angular/nextjs) as foreign
|
|
16
|
+
# ============================================================================
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
|
|
19
|
+
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
20
|
+
PASS=0; FAIL=0; WARN=0
|
|
21
|
+
pass() { echo -e "${GREEN}✅ PASS${NC} $1"; PASS=$((PASS+1)); }
|
|
22
|
+
fail() { echo -e "${RED}❌ FAIL${NC} $1"; FAIL=$((FAIL+1)); }
|
|
23
|
+
warn() { echo -e "${YELLOW}⚠️ WARN${NC} $1"; WARN=$((WARN+1)); }
|
|
24
|
+
|
|
25
|
+
STRICT=0
|
|
26
|
+
[ "${1:-}" = "--strict" ] && STRICT=1
|
|
27
|
+
|
|
28
|
+
echo "============================================"
|
|
29
|
+
echo " Language Consistency (architecture docs)"
|
|
30
|
+
echo "============================================"
|
|
31
|
+
|
|
32
|
+
# ── Resolve project language ────────────────────────────────────────────────
|
|
33
|
+
LANG_NAME=""
|
|
34
|
+
if [ -f "guardian-manifest.json" ]; then
|
|
35
|
+
LANG_NAME=$(python3 -c "import json;print(json.load(open('guardian-manifest.json')).get('language',''))" 2>/dev/null || true)
|
|
36
|
+
fi
|
|
37
|
+
if [ -z "$LANG_NAME" ] && [ -f ".pi/guardian-manifest.json" ]; then
|
|
38
|
+
LANG_NAME=$(python3 -c "import json;print(json.load(open('.pi/guardian-manifest.json')).get('language',''))" 2>/dev/null || true)
|
|
39
|
+
fi
|
|
40
|
+
if [ -z "$LANG_NAME" ]; then
|
|
41
|
+
echo "no manifest language — skipping"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
echo "Project language: $LANG_NAME"
|
|
45
|
+
echo ""
|
|
46
|
+
|
|
47
|
+
case "$LANG_NAME" in
|
|
48
|
+
typescript|nextjs|angular) EXPECTED="typescript|tsx|ts|javascript|jsx|js" ;;
|
|
49
|
+
python) EXPECTED="python|py" ;;
|
|
50
|
+
rust) EXPECTED="rust" ;;
|
|
51
|
+
go) EXPECTED="go" ;;
|
|
52
|
+
java) EXPECTED="java" ;;
|
|
53
|
+
*) EXPECTED="" ;;
|
|
54
|
+
esac
|
|
55
|
+
|
|
56
|
+
if [ -z "$EXPECTED" ]; then
|
|
57
|
+
echo "unsupported language '$LANG_NAME' — skipping"
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
ALL_FENCES="typescript|tsx|ts|javascript|jsx|js|python|py|rust|go|java|kotlin|kt|csharp|cs|ruby|rb|php|swift|cpp|c\+\+|c|scala|elixir|dart"
|
|
62
|
+
ALLOWED_NEUTRAL="json|jsonc|yaml|yml|toml|bash|sh|shell|zsh|sql|mermaid|text|txt|plaintext|dockerfile|docker|http|diff|ini|properties|xml|html|css|scss|makefile|cmake|gradle|protobuf|proto|csv|regex|"
|
|
63
|
+
|
|
64
|
+
is_expected() {
|
|
65
|
+
local f="$1"
|
|
66
|
+
[ -z "$f" ] && return 1 # bare ``` with no language
|
|
67
|
+
case "$EXPECTED" in
|
|
68
|
+
*"|$f|"*|"$f|"*) return 0 ;;
|
|
69
|
+
esac
|
|
70
|
+
# substring match for multi-alias lists
|
|
71
|
+
echo "$EXPECTED" | tr '|' '\n' | grep -qx "$f" && return 0
|
|
72
|
+
return 1
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
is_neutral() {
|
|
76
|
+
local f="$1"
|
|
77
|
+
[ -z "$f" ] && return 0
|
|
78
|
+
echo "$ALLOWED_NEUTRAL" | tr '|' '\n' | grep -qx "$f"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ROOT=".pi/architecture"
|
|
82
|
+
[ -d "$ROOT" ] || { echo "no $ROOT — nothing to check"; exit 0; }
|
|
83
|
+
|
|
84
|
+
FILES=$(find "$ROOT" -name "*.md" -type f 2>/dev/null | grep -v "/modules/module-template.md$")
|
|
85
|
+
MISMATCHES=0
|
|
86
|
+
CHECKED=0
|
|
87
|
+
for doc in $FILES; do
|
|
88
|
+
CHECKED=$((CHECKED+1))
|
|
89
|
+
# Collect fenced block languages
|
|
90
|
+
fences=$(grep -oE '^```[a-zA-Z0-9_+-]*' "$doc" 2>/dev/null | sed 's/^```//' | sort -u)
|
|
91
|
+
[ -z "$fences" ] && continue
|
|
92
|
+
bad=""
|
|
93
|
+
for f in $fences; do
|
|
94
|
+
lower=$(echo "$f" | tr '[:upper:]' '[:lower:]')
|
|
95
|
+
if is_neutral "$lower"; then continue; fi
|
|
96
|
+
if is_expected "$lower"; then continue; fi
|
|
97
|
+
# A fence that is a known programming language but not this project's
|
|
98
|
+
if echo "$lower" | grep -qE "^($ALL_FENCES)$"; then
|
|
99
|
+
bad="$bad $lower"
|
|
100
|
+
elif [ "$STRICT" = "1" ]; then
|
|
101
|
+
bad="$bad $lower"
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
if [ -n "$bad" ]; then
|
|
105
|
+
fail "$doc uses foreign code fence(s):$(echo "$bad" | tr ' ' '\n' | sed '/^$/d' | sort -u | sed 's/^/ /' | tr '\n' ',' | sed 's/,$//') (project: $LANG_NAME)"
|
|
106
|
+
MISMATCHES=$((MISMATCHES+1))
|
|
107
|
+
fi
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
if [ "$MISMATCHES" -eq 0 ]; then
|
|
111
|
+
pass "$CHECKED architecture doc(s) use $LANG_NAME (no foreign code fences)"
|
|
112
|
+
else
|
|
113
|
+
fail "$MISMATCHES of $CHECKED architecture doc(s) contain foreign-language code samples"
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
echo ""
|
|
117
|
+
echo "============================================"
|
|
118
|
+
echo " Summary"
|
|
119
|
+
echo "============================================"
|
|
120
|
+
echo " Passed: $PASS Failed: $FAIL Warnings: $WARN"
|
|
121
|
+
[ "$FAIL" -eq 0 ] && exit 0
|
|
122
|
+
exit 1
|
|
@@ -13,10 +13,28 @@ detect_language() {
|
|
|
13
13
|
|
|
14
14
|
LANG=$(detect_language)
|
|
15
15
|
LANG_SCRIPT="${SCRIPT_DIR}/languages/${LANG}/validate-architecture.sh"
|
|
16
|
+
CONSISTENCY_SCRIPT="${SCRIPT_DIR}/check_language_consistency.sh"
|
|
17
|
+
|
|
18
|
+
# Language consistency runs for EVERY language (architecture docs must use the
|
|
19
|
+
# project language — catches scaffold stubs emitted in the wrong language).
|
|
20
|
+
run_consistency() {
|
|
21
|
+
if [ -f "$CONSISTENCY_SCRIPT" ]; then
|
|
22
|
+
bash "$CONSISTENCY_SCRIPT" || CONSISTENCY_RC=1
|
|
23
|
+
echo ""
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
16
26
|
|
|
17
27
|
if [ -f "$LANG_SCRIPT" ]; then
|
|
18
|
-
|
|
28
|
+
bash "$LANG_SCRIPT" "$@"
|
|
29
|
+
LANG_RC=$?
|
|
30
|
+
CONSISTENCY_RC=0
|
|
31
|
+
run_consistency
|
|
32
|
+
[ "$LANG_RC" -ne 0 ] && exit "$LANG_RC"
|
|
33
|
+
[ "${CONSISTENCY_RC:-0}" -ne 0 ] && exit 1
|
|
34
|
+
exit 0
|
|
19
35
|
else
|
|
20
36
|
echo "No architecture validator for language: $LANG"
|
|
21
|
-
|
|
37
|
+
CONSISTENCY_RC=0
|
|
38
|
+
run_consistency
|
|
39
|
+
exit "${CONSISTENCY_RC:-0}"
|
|
22
40
|
fi
|