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 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.142", 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;
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 += "```rust\n";
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 modules = options.dryRun ? ["billing", "notifications", "shared"] : discoverModules(archDir);
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
- console.log(` \u2705 ${structurePlan.files.length} files created (${structurePlan.modules.length} modules \xD7 ${structurePlan.layers.length} layers)
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.142", 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;
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 += "```rust\n";
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 modules = options.dryRun ? ["billing", "notifications", "shared"] : discoverModules(archDir);
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
- console.log(` ✅ ${structurePlan.files.length} files created (${structurePlan.modules.length} modules × ${structurePlan.layers.length} layers)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.142",
3
+ "version": "0.1.144",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -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
- - Each `## Phase N` section defines one delivery phase
17
- - `### Modules` table lists ONLY the bounded contexts built in this phase
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 all `### Modules` tables MUST equal the number of module docs.",
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: Foundation — contexts with no upstream dependencies",
1760
- "- Phase 1: contexts whose dependencies were built in Phase 0, and so on",
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
- : "Project scaffolded. Next: create architecture module docs and use /architect to plan epics.",
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
- " Scaffold project from architecture decisions",
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-09-25T19:38:51Z",
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": 1,
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
- exec bash "$LANG_SCRIPT" "$@"
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
- exit 0
37
+ CONSISTENCY_RC=0
38
+ run_consistency
39
+ exit "${CONSISTENCY_RC:-0}"
22
40
  fi