sistema-multiagente-sdlc 1.2.0

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.
Files changed (151) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/LICENSE +21 -0
  3. package/README.md +155 -0
  4. package/bin/sdlc.js +4 -0
  5. package/migrations/1.0.1/README.md +28 -0
  6. package/migrations/1.0.1/up.mjs +6 -0
  7. package/migrations/1.1.0/up.mjs +8 -0
  8. package/migrations/1.2.0/up.mjs +17 -0
  9. package/package.json +63 -0
  10. package/profiles/greenfield-sdd/openspec.config.yaml +15 -0
  11. package/profiles/legacy-brownfield-sdd/openspec.config.yaml +16 -0
  12. package/schemas/sdlc.config.schema.json +106 -0
  13. package/scripts/install-sistema-sdlc.ps1 +22 -0
  14. package/scripts/install-sistema-sdlc.sh +33 -0
  15. package/scripts/validate-agent-persona-schema.mjs +44 -0
  16. package/scripts/validate-config-schema.mjs +13 -0
  17. package/scripts/validate-docs-links-exist.mjs +51 -0
  18. package/scripts/validate-external-tools-policy.mjs +35 -0
  19. package/scripts/validate-governance-precedence.mjs +32 -0
  20. package/scripts/validate-manifest-integrity.mjs +49 -0
  21. package/scripts/validate-models-schema.mjs +42 -0
  22. package/scripts/validate-mustache-references-exist.mjs +44 -0
  23. package/scripts/validate-no-inline-managed-content.mjs +92 -0
  24. package/scripts/validate-no-personal-paths.mjs +28 -0
  25. package/scripts/validate-no-placeholder-scripts.mjs +33 -0
  26. package/scripts/validate-openspec-consistency.mjs +38 -0
  27. package/scripts/validate-skill-manifest-consistency.mjs +28 -0
  28. package/scripts/validate-template-sanitization.mjs +44 -0
  29. package/src/cli.js +671 -0
  30. package/src/config-validator.js +30 -0
  31. package/src/file-utils.js +87 -0
  32. package/src/migrations.js +43 -0
  33. package/src/render.js +93 -0
  34. package/src/template-loader.js +158 -0
  35. package/templates/.agents/skills/.gitkeep +1 -0
  36. package/templates/.claude/skills/.gitkeep +1 -0
  37. package/templates/.claude/skills/commit/SKILL.md +18 -0
  38. package/templates/.claude/skills/contexto-proyecto/SKILL.md +15 -0
  39. package/templates/.claude/skills/enrich-us/SKILL.md +23 -0
  40. package/templates/.github/AGENTS.md +29 -0
  41. package/templates/.github/agent-state/README.md +19 -0
  42. package/templates/.github/agent-state/active-slices.yaml +21 -0
  43. package/templates/.github/agent-state/calibration/.gitkeep +1 -0
  44. package/templates/.github/agent-state/calibration/SCHEMA.md +22 -0
  45. package/templates/.github/agent-state/current-slice.md +39 -0
  46. package/templates/.github/agent-state/drafts/.gitkeep +1 -0
  47. package/templates/.github/agent-state/handoffs/.gitkeep +1 -0
  48. package/templates/.github/agent-state/handoffs/TEMPLATE.md +21 -0
  49. package/templates/.github/agent-state/open-decisions.md +13 -0
  50. package/templates/.github/agent-state/open-risks.md +16 -0
  51. package/templates/.github/agent-state/phase-graph.yaml +192 -0
  52. package/templates/.github/agent-state/phase-status.yaml +32 -0
  53. package/templates/.github/agent-state/platform-context.json +12 -0
  54. package/templates/.github/agent-state/telemetry/.gitkeep +1 -0
  55. package/templates/.github/agent-state/telemetry/SCHEMA.md +11 -0
  56. package/templates/.github/agent-state/templates/handoff.md +23 -0
  57. package/templates/.github/agent-state/templates/issue-rework.md +19 -0
  58. package/templates/.github/agent-state/templates/phase-gate.md +7 -0
  59. package/templates/.github/agents/README.md +19 -0
  60. package/templates/.github/agents/analista-requisitos.agent.md +25 -0
  61. package/templates/.github/agents/analista-requisitos.md +24 -0
  62. package/templates/.github/agents/api-agent.md +22 -0
  63. package/templates/.github/agents/api-nestjs.agent.md +25 -0
  64. package/templates/.github/agents/arquitecto-modular-clean.agent.md +25 -0
  65. package/templates/.github/agents/arquitecto.md +22 -0
  66. package/templates/.github/agents/mobile-sync.agent.md +25 -0
  67. package/templates/.github/agents/orquestador-opus.agent.md +25 -0
  68. package/templates/.github/agents/orquestador-opus.md +18 -0
  69. package/templates/.github/agents/ownership-matrix.md +11 -0
  70. package/templates/.github/agents/planificador-opus.agent.md +25 -0
  71. package/templates/.github/agents/planificador-opus.md +18 -0
  72. package/templates/.github/agents/qa-security-review.agent.md +25 -0
  73. package/templates/.github/agents/qa-security-review.md +18 -0
  74. package/templates/.github/agents/semantic-guardrails.json +30 -0
  75. package/templates/.github/agents/surface-traceability.json +52 -0
  76. package/templates/.github/agents/web-admin.agent.md +25 -0
  77. package/templates/.github/agents/web-agent.md +22 -0
  78. package/templates/.github/copilot-instructions-greenfield.md +22 -0
  79. package/templates/.github/copilot-instructions-legacy.md +22 -0
  80. package/templates/.github/copilot-instructions.md +34 -0
  81. package/templates/.github/skills/backend-audit/SKILL.md +57 -0
  82. package/templates/.github/skills/commit/SKILL.md +18 -0
  83. package/templates/.github/skills/contexto-proyecto/SKILL.md +15 -0
  84. package/templates/.github/skills/documentacion-viva/SKILL.md +82 -0
  85. package/templates/.github/skills/enrich-us/SKILL.md +23 -0
  86. package/templates/.github/skills/openspec-apply/SKILL.md +17 -0
  87. package/templates/.github/skills/openspec-archive/SKILL.md +15 -0
  88. package/templates/.github/skills/openspec-continue/SKILL.md +68 -0
  89. package/templates/.github/skills/openspec-explore/SKILL.md +15 -0
  90. package/templates/.github/skills/openspec-ff/SKILL.md +76 -0
  91. package/templates/.github/skills/openspec-new/SKILL.md +64 -0
  92. package/templates/.github/skills/openspec-propose/SKILL.md +14 -0
  93. package/templates/.github/skills/openspec-sync/SKILL.md +64 -0
  94. package/templates/.github/skills/openspec-verify/SKILL.md +16 -0
  95. package/templates/.github/skills/operacion-cli-devops/SKILL.md +77 -0
  96. package/templates/.github/skills/orquestacion-multiagente/SKILL.md +21 -0
  97. package/templates/.github/skills/ui-ux-diseno/SKILL.md +59 -0
  98. package/templates/.sdlc/README.md +7 -0
  99. package/templates/.windsurf/skills/.gitkeep +1 -0
  100. package/templates/AGENTS.md +11 -0
  101. package/templates/CLAUDE.md +26 -0
  102. package/templates/docs/agents/catalogo-comandos-proyecto.md +42 -0
  103. package/templates/docs/agents/domain.md +14 -0
  104. package/templates/docs/agents/external-tools-matrix.md +120 -0
  105. package/templates/docs/agents/guardrails-semanticos-del-dominio.md +21 -0
  106. package/templates/docs/agents/issue-tracker.md +9 -0
  107. package/templates/docs/agents/mapa-de-agentes-y-handoffs.md +17 -0
  108. package/templates/docs/agents/matriz-de-trazabilidad-por-superficie.md +17 -0
  109. package/templates/docs/agents/presentacion-sistema-multiagente-sdlc.md +44 -0
  110. package/templates/docs/agents/stack-map.md +15 -0
  111. package/templates/docs/agents/triage-labels.md +16 -0
  112. package/templates/docs/guides/adopcion_openspec_sdd.md +155 -0
  113. package/templates/docs/guides/business-production-readiness.md +257 -0
  114. package/templates/docs/guides/memoria-persistente-multiagente.md +196 -0
  115. package/templates/docs/guides/sdlc-multiagente.md +18 -0
  116. package/templates/docs/guides/skills-multi-entorno.md +118 -0
  117. package/templates/graphifyignore +18 -0
  118. package/templates/indice-operativo.md +12 -0
  119. package/templates/manifest.yaml +273 -0
  120. package/templates/openspec/changes/.gitkeep +0 -0
  121. package/templates/openspec/config-greenfield.yaml +24 -0
  122. package/templates/openspec/config-legacy.yaml +24 -0
  123. package/templates/openspec/profiles/expanded.yaml +28 -0
  124. package/templates/openspec/profiles/minimal.yaml +20 -0
  125. package/templates/openspec/schemas/greenfield-sdd/schema.yaml +143 -0
  126. package/templates/openspec/schemas/greenfield-sdd/templates/design.md +27 -0
  127. package/templates/openspec/schemas/greenfield-sdd/templates/proposal.md +42 -0
  128. package/templates/openspec/schemas/greenfield-sdd/templates/spec.md +9 -0
  129. package/templates/openspec/schemas/greenfield-sdd/templates/tasks.md +14 -0
  130. package/templates/openspec/schemas/legacy-brownfield-sdd/schema.yaml +312 -0
  131. package/templates/openspec/schemas/legacy-brownfield-sdd/templates/design.md +31 -0
  132. package/templates/openspec/schemas/legacy-brownfield-sdd/templates/proposal.md +47 -0
  133. package/templates/openspec/schemas/legacy-brownfield-sdd/templates/research.md +119 -0
  134. package/templates/openspec/schemas/legacy-brownfield-sdd/templates/spec.md +9 -0
  135. package/templates/openspec/schemas/legacy-brownfield-sdd/templates/tasks.md +14 -0
  136. package/templates/openspec/specs/.gitkeep +0 -0
  137. package/templates/openspec/specs/business-production-readiness/README.md +27 -0
  138. package/templates/openspec/specs/business-production-readiness/spec.md +72 -0
  139. package/templates/openspec/specs/project-phases/spec.md +62 -0
  140. package/templates/scripts/agent-skills.manifest.json +63 -0
  141. package/templates/scripts/bootstrap-agent-skills.ps1 +137 -0
  142. package/templates/scripts/bootstrap-obsidian-vault.ps1 +186 -0
  143. package/templates/scripts/claude-to-obsidian.py +154 -0
  144. package/templates/scripts/compute-calibration.ps1 +79 -0
  145. package/templates/scripts/continua.ps1 +141 -0
  146. package/templates/scripts/export-graphify-obsidian.py +123 -0
  147. package/templates/scripts/models.yaml +30 -0
  148. package/templates/scripts/obsidian-memory.config.example.json +39 -0
  149. package/templates/scripts/publish-trace.ps1 +179 -0
  150. package/templates/scripts/register-claude-sync-task.ps1 +52 -0
  151. package/templates/scripts/sync-claude-obsidian.ps1 +73 -0
@@ -0,0 +1,44 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { listFiles } from "../src/file-utils.js";
4
+
5
+ const root = process.cwd();
6
+ const files = listFiles(root).filter((file) => /^templates\/\.github\/agents\/.+\.agent\.md$/.test(file));
7
+ const required = ["name", "model", "role", "phases", "inputs", "outputs"];
8
+ const errors = [];
9
+
10
+ function parseFrontmatter(content) {
11
+ const match = /^---\n([\s\S]*?)\n---/.exec(content);
12
+ if (!match) return null;
13
+ const out = {};
14
+ for (const line of match[1].split("\n")) {
15
+ const item = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line.trim());
16
+ if (item) out[item[1]] = item[2];
17
+ }
18
+ return out;
19
+ }
20
+
21
+ for (const file of files) {
22
+ const content = fs.readFileSync(path.join(root, file), "utf8");
23
+ const frontmatter = parseFrontmatter(content);
24
+ if (!frontmatter) {
25
+ errors.push(`${file}: missing YAML frontmatter`);
26
+ continue;
27
+ }
28
+ for (const field of required) {
29
+ if (!frontmatter[field]) errors.push(`${file}: missing ${field}`);
30
+ }
31
+ for (const listField of ["phases", "inputs", "outputs"]) {
32
+ if (frontmatter[listField] && !/^\[[^\]]*\]$/.test(frontmatter[listField])) {
33
+ errors.push(`${file}: ${listField} must be an inline array`);
34
+ }
35
+ }
36
+ }
37
+
38
+ if (errors.length > 0) {
39
+ console.error("Agent persona schema validation: FAIL");
40
+ for (const error of errors) console.error(`- ${error}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log(`Agent persona schema validation: PASS (${files.length} personas)`);
@@ -0,0 +1,13 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { validateConfigShape } from "../src/render.js";
3
+
4
+ const config = JSON.parse(readFileSync(new URL("../sdlc.config.example.json", import.meta.url), "utf8"));
5
+ const errors = validateConfigShape(config);
6
+
7
+ if (errors.length > 0) {
8
+ console.error("Config schema validation: FAIL");
9
+ for (const error of errors) console.error(`- ${error}`);
10
+ process.exit(1);
11
+ }
12
+
13
+ console.log("Config schema validation: PASS");
@@ -0,0 +1,51 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { listFiles } from "../src/file-utils.js";
4
+
5
+ const root = process.cwd();
6
+ const checkUrls = process.argv.includes("--check-urls");
7
+ const mdFiles = listFiles(root).filter((file) => file.endsWith(".md") && !file.includes("node_modules/"));
8
+ const errors = [];
9
+
10
+ function stripCodeBlocks(content) {
11
+ return content.replace(/```[\s\S]*?```/g, "");
12
+ }
13
+
14
+ function shouldSkip(target) {
15
+ return (
16
+ target.startsWith("#") ||
17
+ target.startsWith("mailto:") ||
18
+ target.startsWith("http://") ||
19
+ target.startsWith("https://") ||
20
+ target.includes("{{") ||
21
+ target.includes("<") ||
22
+ target.includes(">")
23
+ );
24
+ }
25
+
26
+ for (const file of mdFiles) {
27
+ const content = stripCodeBlocks(fs.readFileSync(path.join(root, file), "utf8"));
28
+ const linkPattern = /(?<!!)\[[^\]]+\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
29
+ for (const match of content.matchAll(linkPattern)) {
30
+ const raw = match[1];
31
+ if (shouldSkip(raw)) {
32
+ if (!checkUrls && /^https?:\/\//.test(raw)) continue;
33
+ continue;
34
+ }
35
+ const clean = raw.split("#")[0];
36
+ if (!clean) continue;
37
+ const base = path.dirname(path.join(root, file));
38
+ const resolved = path.resolve(base, clean);
39
+ if (!resolved.startsWith(root) || !fs.existsSync(resolved)) {
40
+ errors.push(`${file}: broken link ${raw}`);
41
+ }
42
+ }
43
+ }
44
+
45
+ if (errors.length > 0) {
46
+ console.error("Docs links validation: FAIL");
47
+ for (const error of errors) console.error(`- ${error}`);
48
+ process.exit(1);
49
+ }
50
+
51
+ console.log(`Docs links validation: PASS (${mdFiles.length} markdown files)`);
@@ -0,0 +1,35 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { listFiles } from "../src/file-utils.js";
4
+
5
+ const root = process.cwd();
6
+ const files = listFiles(root).filter((file) =>
7
+ /(^|\/)(templates\/scripts|scripts)\/.+\.(ps1|py|sh|mjs|js)$/.test(file)
8
+ );
9
+
10
+ const riskyPatterns = [
11
+ /\bnpx\s+(?:--yes\s+)?[\w@./-]+\s+(?:add|install)\b/i,
12
+ /\bpip\s+install\b/i,
13
+ /\bpython\s+-m\s+pip\s+install\b/i,
14
+ /\bRegister-ScheduledTask\b/i,
15
+ /\bInstall-Module\b/i
16
+ ];
17
+
18
+ const errors = [];
19
+ for (const file of files) {
20
+ const content = fs.readFileSync(path.join(root, file), "utf8");
21
+ const marked = /#\s*opt-in:external/i.test(content);
22
+ for (const pattern of riskyPatterns) {
23
+ if (pattern.test(content) && !marked) {
24
+ errors.push(`${file}: external side-effect command requires # opt-in:external marker (${pattern})`);
25
+ }
26
+ }
27
+ }
28
+
29
+ if (errors.length > 0) {
30
+ console.error("External tools policy validation: FAIL");
31
+ for (const error of errors) console.error(`- ${error}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ console.log("External tools policy validation: PASS");
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const root = process.cwd();
5
+ const canonicalRoot = path.join(root, "templates", ".github", "skills");
6
+ const mirrorRoots = [
7
+ path.join(root, "templates", ".claude", "skills"),
8
+ path.join(root, "templates", ".agents", "skills"),
9
+ path.join(root, "templates", ".windsurf", "skills")
10
+ ];
11
+
12
+ const errors = [];
13
+ for (const mirrorRoot of mirrorRoots) {
14
+ if (!fs.existsSync(mirrorRoot)) continue;
15
+ for (const entry of fs.readdirSync(mirrorRoot, { withFileTypes: true })) {
16
+ if (!entry.isDirectory()) continue;
17
+ const mirrorSkill = path.join(mirrorRoot, entry.name, "SKILL.md");
18
+ if (!fs.existsSync(mirrorSkill)) continue;
19
+ const canonical = path.join(canonicalRoot, entry.name, "SKILL.md");
20
+ if (!fs.existsSync(canonical)) {
21
+ errors.push(`${path.relative(root, mirrorSkill)} exists without canonical .github/skills/${entry.name}/SKILL.md`);
22
+ }
23
+ }
24
+ }
25
+
26
+ if (errors.length > 0) {
27
+ console.error("Governance precedence validation: FAIL");
28
+ for (const error of errors) console.error(`- ${error}`);
29
+ process.exit(1);
30
+ }
31
+
32
+ console.log("Governance precedence validation: PASS");
@@ -0,0 +1,49 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { sha256File } from "../src/file-utils.js";
4
+
5
+ const targetArgIndex = process.argv.indexOf("--target");
6
+ const target = targetArgIndex >= 0 ? path.resolve(process.argv[targetArgIndex + 1]) : process.cwd();
7
+ const allowEmpty = process.argv.includes("--allow-empty");
8
+ const manifests = [];
9
+
10
+ function walk(current) {
11
+ if (!fs.existsSync(current)) return;
12
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
13
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
14
+ const absolute = path.join(current, entry.name);
15
+ if (entry.isDirectory()) walk(absolute);
16
+ if (entry.isFile() && entry.name === "install-manifest.json" && path.basename(path.dirname(absolute)) === ".sdlc") {
17
+ manifests.push(absolute);
18
+ }
19
+ }
20
+ }
21
+
22
+ walk(target);
23
+
24
+ const errors = [];
25
+ for (const manifest of manifests) {
26
+ const checksumPath = path.join(path.dirname(manifest), "install-manifest.sha256");
27
+ if (!fs.existsSync(checksumPath)) {
28
+ errors.push(`${manifest}: falta install-manifest.sha256`);
29
+ continue;
30
+ }
31
+ const expected = fs.readFileSync(checksumPath, "utf8").trim();
32
+ const actual = sha256File(manifest);
33
+ if (expected !== actual) {
34
+ errors.push(`${manifest}: checksum invalido`);
35
+ }
36
+ }
37
+
38
+ if (errors.length > 0) {
39
+ console.error("Manifest integrity validation: FAIL");
40
+ for (const error of errors) console.error(`- ${error}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ if (manifests.length === 0) {
45
+ console.warn("Manifest integrity validation: WARN — 0 manifest(s) encontrados (ningun repo instalado bajo --target)");
46
+ process.exit(allowEmpty ? 0 : 2);
47
+ }
48
+
49
+ console.log(`Manifest integrity validation: PASS (${manifests.length} manifest(s))`);
@@ -0,0 +1,42 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const root = process.cwd();
5
+ const modelsPath = path.join(root, "templates", "scripts", "models.yaml");
6
+ const content = fs.readFileSync(modelsPath, "utf8");
7
+ const errors = [];
8
+
9
+ function requirePattern(pattern, message) {
10
+ if (!pattern.test(content)) errors.push(message);
11
+ }
12
+
13
+ requirePattern(/^version:\s*1$/m, "version must be 1");
14
+ requirePattern(/^updated:\s*"\d{4}-\d{2}-\d{2}"$/m, "updated must be quoted YYYY-MM-DD");
15
+ requirePattern(/^roles:\s*$/m, "roles block is required");
16
+ requirePattern(/^platforms:\s*$/m, "platforms block is required");
17
+ requirePattern(/^fallbacks:\s*$/m, "fallbacks block is required");
18
+
19
+ for (const role of ["orquestador", "planificador", "developer", "reviewer"]) {
20
+ requirePattern(new RegExp(`^ ${role}:\\s*$`, "m"), `role ${role} is required`);
21
+ requirePattern(new RegExp(`^ ${role}:\\s*\\n primary:\\s*\\S+\\n fallback:\\s*\\S+`, "m"), `role ${role} requires primary and fallback`);
22
+ }
23
+
24
+ for (const platform of ["claude_code", "codex", "copilot", "windsurf"]) {
25
+ requirePattern(new RegExp(`^ ${platform}:\\s*\\n default:\\s*\\S+`, "m"), `platform ${platform} requires default`);
26
+ }
27
+
28
+ const allowedTopLevel = new Set(["version", "updated", "roles", "platforms", "fallbacks"]);
29
+ for (const line of content.split("\n")) {
30
+ const match = /^([A-Za-z0-9_-]+):/.exec(line);
31
+ if (match && !allowedTopLevel.has(match[1])) {
32
+ errors.push(`unexpected top-level key: ${match[1]}`);
33
+ }
34
+ }
35
+
36
+ if (errors.length > 0) {
37
+ console.error("Models schema validation: FAIL");
38
+ for (const error of errors) console.error(`- ${error}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ console.log("Models schema validation: PASS");
@@ -0,0 +1,44 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { defaultConfig } from "../src/render.js";
4
+ import { listFiles } from "../src/file-utils.js";
5
+
6
+ const root = process.cwd();
7
+ const context = defaultConfig({
8
+ target: root,
9
+ mode: "greenfield",
10
+ projectName: "Example Project",
11
+ projectSlug: "example-project"
12
+ });
13
+ context.surfacesTable = "| `backend` | `apps/api` | `api-agent` |";
14
+ context.surfacesList = "- `apps/api`";
15
+
16
+ const files = listFiles(path.join(root, "templates")).filter((file) => {
17
+ const absolute = path.join(root, "templates", file);
18
+ return fs.statSync(absolute).isFile();
19
+ });
20
+
21
+ const errors = [];
22
+ for (const file of files) {
23
+ const absolute = path.join(root, "templates", file);
24
+ const content = fs.readFileSync(absolute, "utf8");
25
+ for (const match of content.matchAll(/\{\{\s*([\w.]+)\s*\}\}/g)) {
26
+ const expr = match[1];
27
+ const value = expr.split(".").reduce((obj, key) => {
28
+ if (obj == null) return undefined;
29
+ if (/^\d+$/.test(key) && Array.isArray(obj)) return obj[Number(key)];
30
+ return obj[key];
31
+ }, context);
32
+ if (value == null) {
33
+ errors.push(`${path.relative(root, absolute)}: unresolved {{${expr}}}`);
34
+ }
35
+ }
36
+ }
37
+
38
+ if (errors.length > 0) {
39
+ console.error("Mustache references validation: FAIL");
40
+ for (const error of errors) console.error(`- ${error}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log("Mustache references validation: PASS");
@@ -0,0 +1,92 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
6
+ const targetFile = path.join(repoRoot, "src", "render.js");
7
+
8
+ const MAX_LITERAL_LENGTH = 300;
9
+ const MAX_LITERAL_NEWLINES = 5;
10
+
11
+ const source = fs.readFileSync(targetFile, "utf8");
12
+
13
+ function findTemplateLiterals(src) {
14
+ const literals = [];
15
+ let i = 0;
16
+ while (i < src.length) {
17
+ const ch = src[i];
18
+ if (ch === "/" && src[i + 1] === "/") {
19
+ while (i < src.length && src[i] !== "\n") i++;
20
+ continue;
21
+ }
22
+ if (ch === "/" && src[i + 1] === "*") {
23
+ i += 2;
24
+ while (i < src.length && !(src[i] === "*" && src[i + 1] === "/")) i++;
25
+ i += 2;
26
+ continue;
27
+ }
28
+ if (ch === '"' || ch === "'") {
29
+ const quote = ch;
30
+ i++;
31
+ while (i < src.length && src[i] !== quote) {
32
+ if (src[i] === "\\") i += 2;
33
+ else i++;
34
+ }
35
+ i++;
36
+ continue;
37
+ }
38
+ if (ch === "`") {
39
+ const start = i;
40
+ let lineStart = src.lastIndexOf("\n", start) + 1;
41
+ const startLine = src.slice(0, start).split("\n").length;
42
+ i++;
43
+ let newlines = 0;
44
+ while (i < src.length && src[i] !== "`") {
45
+ if (src[i] === "\\" && i + 1 < src.length) {
46
+ i += 2;
47
+ continue;
48
+ }
49
+ if (src[i] === "$" && src[i + 1] === "{") {
50
+ let depth = 1;
51
+ i += 2;
52
+ while (i < src.length && depth > 0) {
53
+ if (src[i] === "{") depth++;
54
+ else if (src[i] === "}") depth--;
55
+ if (src[i] === "\n") newlines++;
56
+ if (depth > 0) i++;
57
+ }
58
+ i++;
59
+ continue;
60
+ }
61
+ if (src[i] === "\n") newlines++;
62
+ i++;
63
+ }
64
+ const length = i - start - 1;
65
+ literals.push({ startLine, length, newlines });
66
+ i++;
67
+ continue;
68
+ }
69
+ i++;
70
+ }
71
+ return literals;
72
+ }
73
+
74
+ const literals = findTemplateLiterals(source);
75
+ const violations = literals.filter(
76
+ (lit) => lit.length > MAX_LITERAL_LENGTH || lit.newlines > MAX_LITERAL_NEWLINES
77
+ );
78
+
79
+ if (violations.length > 0) {
80
+ console.error("No inline managed content validation: FAIL");
81
+ console.error(`src/render.js contiene template literals largos (potencial regresion al patron inline pre-Fase A).`);
82
+ console.error(`Limites: length<=${MAX_LITERAL_LENGTH}, newlines<=${MAX_LITERAL_NEWLINES}.`);
83
+ for (const violation of violations) {
84
+ console.error(`- line ${violation.startLine}: length=${violation.length}, newlines=${violation.newlines}`);
85
+ }
86
+ console.error("Mueva contenido a templates/ y referencielo via templates/manifest.yaml.");
87
+ process.exit(1);
88
+ }
89
+
90
+ console.log(
91
+ `No inline managed content validation: PASS (${literals.length} template literal(s), max length=${literals.reduce((m, l) => Math.max(m, l.length), 0)}, max newlines=${literals.reduce((m, l) => Math.max(m, l.newlines), 0)})`
92
+ );
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { listFiles } from "../src/file-utils.js";
4
+
5
+ const root = process.cwd();
6
+ const patterns = [
7
+ /C:\\Users\\/i,
8
+ /\/Users\/[^/\s]+\/source\/repos/i
9
+ ];
10
+ const errors = [];
11
+
12
+ for (const file of listFiles(root)) {
13
+ const absolute = path.join(root, file);
14
+ const content = fs.readFileSync(absolute, "utf8");
15
+ for (const pattern of patterns) {
16
+ if (pattern.test(content)) {
17
+ errors.push(`${file} contiene ruta personal: ${pattern}`);
18
+ }
19
+ }
20
+ }
21
+
22
+ if (errors.length > 0) {
23
+ console.error("No personal paths validation: FAIL");
24
+ for (const error of errors) console.error(`- ${error}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ console.log("No personal paths validation: PASS");
@@ -0,0 +1,33 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { listFiles } from "../src/file-utils.js";
4
+
5
+ const root = process.cwd();
6
+ const scriptFiles = listFiles(root).filter((file) =>
7
+ /(^|\/)(templates\/scripts|scripts)\/.+\.(ps1|py|sh|mjs|js)$/.test(file)
8
+ );
9
+
10
+ const patterns = [
11
+ /Write-Host\s+["']?pendiente/i,
12
+ /#\s*TODO:?.*implementar/i,
13
+ /throw\s+["']not implemented/i,
14
+ /pass\s*#\s*TODO/i
15
+ ];
16
+
17
+ const errors = [];
18
+ for (const file of scriptFiles) {
19
+ const content = fs.readFileSync(path.join(root, file), "utf8");
20
+ for (const pattern of patterns) {
21
+ if (pattern.test(content)) {
22
+ errors.push(`${file}: placeholder script marker matched ${pattern}`);
23
+ }
24
+ }
25
+ }
26
+
27
+ if (errors.length > 0) {
28
+ console.error("No placeholder scripts validation: FAIL");
29
+ for (const error of errors) console.error(`- ${error}`);
30
+ process.exit(1);
31
+ }
32
+
33
+ console.log(`No placeholder scripts validation: PASS (${scriptFiles.length} script files)`);
@@ -0,0 +1,38 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const root = process.cwd();
5
+ const specsRoot = path.join(root, "templates", "openspec", "specs");
6
+ const errors = [];
7
+
8
+ if (fs.existsSync(specsRoot)) {
9
+ for (const entry of fs.readdirSync(specsRoot, { withFileTypes: true })) {
10
+ if (!entry.isDirectory()) continue;
11
+ const specPath = path.join(specsRoot, entry.name, "spec.md");
12
+ if (!fs.existsSync(specPath)) continue;
13
+ const content = fs.readFileSync(specPath, "utf8");
14
+ if (!/^#\s+/m.test(content)) errors.push(`${path.relative(root, specPath)}: missing title`);
15
+ if (!/## Requirements/m.test(content)) errors.push(`${path.relative(root, specPath)}: missing ## Requirements`);
16
+ if (!/### Requirement:/m.test(content)) errors.push(`${path.relative(root, specPath)}: missing Requirement blocks`);
17
+ if (!/#### Scenario:/m.test(content)) errors.push(`${path.relative(root, specPath)}: missing Scenario blocks`);
18
+ }
19
+ }
20
+
21
+ const changesRoot = path.join(root, "templates", "openspec", "changes");
22
+ if (fs.existsSync(changesRoot)) {
23
+ for (const entry of fs.readdirSync(changesRoot, { withFileTypes: true })) {
24
+ if (!entry.isDirectory()) continue;
25
+ const archivedMarker = path.join(changesRoot, entry.name, "archived.json");
26
+ if (fs.existsSync(archivedMarker)) {
27
+ errors.push(`archived change must live under archive/: ${path.relative(root, archivedMarker)}`);
28
+ }
29
+ }
30
+ }
31
+
32
+ if (errors.length > 0) {
33
+ console.error("OpenSpec consistency validation: FAIL");
34
+ for (const error of errors) console.error(`- ${error}`);
35
+ process.exit(1);
36
+ }
37
+
38
+ console.log("OpenSpec consistency validation: PASS");
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ const root = process.cwd();
5
+ const skillsRoot = path.join(root, "templates", ".github", "skills");
6
+ const manifestPath = path.join(root, "templates", "scripts", "agent-skills.manifest.json");
7
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
8
+
9
+ const actual = fs
10
+ .readdirSync(skillsRoot, { withFileTypes: true })
11
+ .filter((entry) => entry.isDirectory() && fs.existsSync(path.join(skillsRoot, entry.name, "SKILL.md")))
12
+ .map((entry) => entry.name)
13
+ .sort();
14
+
15
+ const allowed = [...(manifest.repoGovernedSkills ?? [])].sort();
16
+ const missing = actual.filter((skill) => !allowed.includes(skill));
17
+ const stale = allowed.filter((skill) => !actual.includes(skill));
18
+ const errors = [];
19
+ if (missing.length > 0) errors.push(`missing from agent-skills.manifest.json: ${missing.join(", ")}`);
20
+ if (stale.length > 0) errors.push(`manifest entries without .github skill: ${stale.join(", ")}`);
21
+
22
+ if (errors.length > 0) {
23
+ console.error("Skill manifest consistency validation: FAIL");
24
+ for (const error of errors) console.error(`- ${error}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ console.log(`Skill manifest consistency validation: PASS (${actual.length} skills)`);
@@ -0,0 +1,44 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { listFiles } from "../src/file-utils.js";
4
+
5
+ const root = process.cwd();
6
+ const allowed = [
7
+ /^docs\/extraction\//,
8
+ /^docs\/adr\//,
9
+ /^CHANGELOG\.md$/
10
+ ];
11
+ const forbidden = [
12
+ /\bFacturacionDian\b/i,
13
+ /\bSamcol\b/i,
14
+ /\bFERRELECTRICOS\b/i,
15
+ /\bPasarelaDePago\b/i,
16
+ /\bpayment-core\b/i,
17
+ /\bPSE\b/
18
+ ];
19
+ const errors = [];
20
+
21
+ function isAllowed(file) {
22
+ return allowed.some((pattern) => pattern.test(file));
23
+ }
24
+
25
+ for (const file of listFiles(root)) {
26
+ if (isAllowed(file)) {
27
+ continue;
28
+ }
29
+ const absolute = path.join(root, file);
30
+ const content = fs.readFileSync(absolute, "utf8");
31
+ for (const pattern of forbidden) {
32
+ if (pattern.test(content)) {
33
+ errors.push(`${file} contiene termino no sanitizado: ${pattern}`);
34
+ }
35
+ }
36
+ }
37
+
38
+ if (errors.length > 0) {
39
+ console.error("Template sanitization validation: FAIL");
40
+ for (const error of errors) console.error(`- ${error}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log("Template sanitization validation: PASS");