@aifabrix/builder 2.52.1 → 2.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/lib/app/deploy-config.js +3 -2
  2. package/lib/channels/add-channel.js +11 -41
  3. package/lib/channels/approval-guides/index.js +3 -0
  4. package/lib/channels/approval-guides/microsoft-copilot.js +26 -17
  5. package/lib/channels/channel-add-existing.js +72 -0
  6. package/lib/channels/channel-runtime-resolver.js +5 -1
  7. package/lib/channels/channel-status-display.js +30 -3
  8. package/lib/channels/microsoft-copilot-entra-sso-form.js +73 -2
  9. package/lib/channels/microsoft-copilot-entra-sso.js +26 -5
  10. package/lib/channels/microsoft-copilot-limits.js +22 -3
  11. package/lib/channels/microsoft-copilot-package.js +59 -14
  12. package/lib/channels/microsoft-copilot-validate.js +40 -10
  13. package/lib/cli/attach-default-help-examples.js +102 -0
  14. package/lib/cli/index.js +2 -0
  15. package/lib/cli/setup-credential-deployment.js +7 -5
  16. package/lib/cli/setup-utility-validate.js +1 -0
  17. package/lib/commands/credential-push.js +37 -5
  18. package/lib/commands/package-score.js +72 -0
  19. package/lib/commands/role-assistant.js +46 -8
  20. package/lib/deployment/deployer-poll-run.js +2 -2
  21. package/lib/deployment/deployer.js +1 -1
  22. package/lib/deployment/environment.js +2 -2
  23. package/lib/external-system/deploy.js +11 -21
  24. package/lib/integration-definition/apply.js +12 -14
  25. package/lib/integration-definition/enrich-openapi-operations.js +9 -10
  26. package/lib/lab/package-parity-score.js +417 -0
  27. package/lib/programmatic/capability-registry.js +1 -0
  28. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  29. package/lib/programmatic/help-content/cip-overview.md +27 -0
  30. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  31. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  32. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  33. package/lib/programmatic/help-content/overview.md +25 -0
  34. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  35. package/lib/programmatic/help-content/workflow.md +50 -0
  36. package/lib/programmatic/openapi-component-schemas.js +1 -0
  37. package/lib/programmatic/openapi-descriptions.js +6 -0
  38. package/lib/programmatic/openapi-envelope-examples.js +11 -0
  39. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  40. package/lib/programmatic/openapi-request-schemas.js +39 -13
  41. package/lib/programmatic/openapi-result-schemas.js +30 -0
  42. package/lib/programmatic/openapi-schema-descriptions-core.js +18 -0
  43. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  44. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  45. package/lib/programmatic/route-handler-map.js +6 -0
  46. package/lib/programmatic/run-integration-definition.js +13 -5
  47. package/lib/role-assistant/evidence-sync.js +55 -7
  48. package/lib/role-assistant/test-runner-attachments.js +118 -0
  49. package/lib/role-assistant/test-runner-bid-apply.js +100 -21
  50. package/lib/role-assistant/test-runner-bid-document.js +1 -1
  51. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  52. package/lib/role-assistant/test-runner-workhub-answers.js +11 -7
  53. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  54. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  55. package/lib/role-assistant/test-runner-workhub.js +68 -105
  56. package/lib/role-assistant/test-runner.js +5 -63
  57. package/lib/role-assistant/test-suite.js +33 -40
  58. package/lib/schema/external-datasource.schema.json +13 -4
  59. package/lib/utils/compose-generator.js +10 -1
  60. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  61. package/lib/utils/docker-build.js +2 -2
  62. package/lib/utils/help-builder.js +2 -1
  63. package/lib/utils/promote-dev-docker-image.js +15 -10
  64. package/package.json +14 -16
  65. package/templates/README.md +12 -1
  66. package/templates/applications/dataplane/application.yaml +8 -1
  67. package/templates/applications/dataplane/env.template +1 -1
  68. package/templates/applications/miso-controller/application.yaml +2 -1
  69. package/templates/applications/miso-controller/rbac.yaml +1 -0
  70. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +6 -2
  71. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +8 -5
  72. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  73. package/templates/channels/microsoft-copilot/install.template.md.hbs +74 -22
  74. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +29 -14
  75. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +6 -2
  76. package/templates/python/docker-compose.hbs +36 -0
  77. package/templates/typescript/docker-compose.hbs +36 -0
  78. package/.cursor/plans/Archive/157.1-security-quality-violations-by-type.csv +0 -374
  79. package/.cursor/process/README.md +0 -9
  80. package/.cursor/rules/anchor-docs.mdc +0 -15
  81. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  82. package/.cursor/rules/cli-layout.mdc +0 -81
  83. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  84. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  85. package/.cursor/rules/docs-rules.mdc +0 -32
  86. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  87. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  88. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  89. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  90. package/.cursor/rules/project-rules.mdc +0 -913
  91. package/.cursor/skills/README.md +0 -15
  92. package/.dockerignore +0 -52
  93. package/.eslintignore +0 -22
  94. package/.markdownlint.json +0 -14
  95. package/anchor-docs/README.md +0 -10
  96. package/anchor-docs/_TEMPLATE +0 -24
  97. package/babel.config.js +0 -6
  98. package/bin/builder-api-cleanup.js +0 -26
  99. package/bin/builder-api-nest.js +0 -10
  100. package/bin/builder-api-spike.js +0 -19
  101. package/enterprise-knowledge/README.md +0 -50
  102. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  103. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  104. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  105. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  106. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  107. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  108. package/integration/README.md +0 -81
  109. package/integration/github/README.md +0 -53
  110. package/integration/github/application.json +0 -21
  111. package/integration/github/deploy.js +0 -72
  112. package/integration/github/env.template +0 -11
  113. package/integration/github/github-datasource-issue-comments.json +0 -239
  114. package/integration/github/github-datasource-issues.json +0 -399
  115. package/integration/github/github-datasource-repository.json +0 -354
  116. package/integration/github/github-deploy.json +0 -1202
  117. package/integration/github/github-system.json +0 -82
  118. package/integration/hubspot-test/README.md +0 -159
  119. package/integration/hubspot-test/application.json +0 -54
  120. package/integration/hubspot-test/companies.json +0 -2048
  121. package/integration/hubspot-test/create-hubspot.js +0 -523
  122. package/integration/hubspot-test/env.template +0 -4
  123. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  124. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  125. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  126. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  127. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  128. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  129. package/integration/hubspot-test/rbac.json +0 -166
  130. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  131. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  132. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  133. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  134. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  135. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  136. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  137. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  138. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  139. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  140. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  141. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  142. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  143. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  144. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  145. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  146. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  147. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  148. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  149. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  150. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  151. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  152. package/integration/hubspot-test/test.js +0 -1588
  153. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  154. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  155. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  156. package/integration/roundtrip-test-local/README.md +0 -143
  157. package/integration/roundtrip-test-local/application.yaml +0 -13
  158. package/integration/roundtrip-test-local/env.template +0 -15
  159. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  160. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  161. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  162. package/integration/roundtrip-test-local2/README.md +0 -143
  163. package/integration/roundtrip-test-local2/application.yaml +0 -13
  164. package/integration/roundtrip-test-local2/env.template +0 -15
  165. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  166. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  167. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  168. package/integration/test/wizard.yaml +0 -8
  169. package/jest.config.coverage.js +0 -37
  170. package/jest.config.default.js +0 -11
  171. package/jest.config.integration.fixtures.js +0 -22
  172. package/jest.config.integration.js +0 -33
  173. package/jest.config.isolated.js +0 -11
  174. package/jest.config.manual.js +0 -30
  175. package/jest.global-live-fabrix-hooks.js +0 -36
  176. package/jest.isolated-projects.js +0 -283
  177. package/jest.projects.js +0 -79
  178. package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +0 -205
  179. package/packages/builder-api/README.md +0 -120
  180. package/packages/builder-api/package.json +0 -30
  181. package/packages/builder-api/src/app.module.ts +0 -7
  182. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  183. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  184. package/packages/builder-api/src/main.ts +0 -16
  185. package/packages/builder-api/tsconfig.json +0 -15
  186. package/scripts/check-builder-api-architecture-grep.js +0 -79
  187. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  188. package/scripts/ci-fix.sh +0 -19
  189. package/scripts/ci-simulate.sh +0 -19
  190. package/scripts/diagnose-cli.js +0 -150
  191. package/scripts/docker_image_content_gate.js +0 -47
  192. package/scripts/docker_image_content_gate.sh +0 -5
  193. package/scripts/export-builder-api-evidence.js +0 -262
  194. package/scripts/install-local.js +0 -490
  195. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  196. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  197. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  198. package/scripts/lib/plan-actor.js +0 -109
  199. package/scripts/lib/plan-config.js +0 -20
  200. package/scripts/lib/plan-gates.js +0 -154
  201. package/scripts/lib/plan-meta.js +0 -104
  202. package/scripts/lib/plan-parse.js +0 -162
  203. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  204. package/scripts/lib/plan-run-core.js +0 -125
  205. package/scripts/lib/plan-run-report.js +0 -119
  206. package/scripts/plan-approve.js +0 -24
  207. package/scripts/plan-archive.js +0 -49
  208. package/scripts/plan-run.js +0 -13
  209. package/scripts/plan-set-actor.js +0 -21
  210. package/scripts/plan-validate.js +0 -80
  211. package/scripts/pnpm/af.mjs +0 -125
  212. package/scripts/pnpm/check-quiet.mjs +0 -32
  213. package/scripts/pnpm/dev-install.mjs +0 -29
  214. package/scripts/pnpm/dev-reload.mjs +0 -18
  215. package/scripts/pnpm/help.mjs +0 -55
  216. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  217. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  218. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  219. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  220. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  221. package/scripts/pnpm/lib/spinner.mjs +0 -42
  222. package/scripts/pnpm-global-remove.js +0 -48
  223. package/scripts/stage_builder_api_for_docker.js +0 -65
  224. package/scripts/sync-builder-api-template.js +0 -44
  225. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -1,104 +0,0 @@
1
- /**
2
- * @fileoverview Resolve plan paths and patch YAML frontmatter.
3
- */
4
-
5
- 'use strict';
6
-
7
- const fs = require('fs');
8
- const path = require('path');
9
- const { REPO_ROOT, PLANS_DIR } = require('./plan-config');
10
-
11
- /**
12
- * @param {string} msg
13
- * @returns {never}
14
- */
15
- function die(msg) {
16
- console.error(`Error: ${msg}`);
17
- process.exit(1);
18
- }
19
-
20
- /**
21
- * @param {string} input
22
- * @returns {string}
23
- */
24
- function resolvePlanPath(input) {
25
- if (!input) {
26
- die('Plan path or id required (e.g. 417.0)');
27
- }
28
- const resolved = path.resolve(REPO_ROOT, input);
29
- if (fs.existsSync(resolved)) {
30
- return resolved;
31
- }
32
- if (!fs.existsSync(PLANS_DIR)) {
33
- die(`Plans directory not found: ${PLANS_DIR}`);
34
- }
35
- /** @param {string} dir */
36
- function findIn(dir) {
37
- if (!fs.existsSync(dir)) {
38
- return null;
39
- }
40
- for (const name of fs.readdirSync(dir)) {
41
- const full = path.join(dir, name);
42
- if (fs.statSync(full).isDirectory()) {
43
- const found = findIn(full);
44
- if (found) {
45
- return found;
46
- }
47
- } else if (name.endsWith('.plan.md')) {
48
- const base = name.slice(0, -'.plan.md'.length);
49
- if (base === input || base.startsWith(`${input}-`)) {
50
- return full;
51
- }
52
- }
53
- }
54
- return null;
55
- }
56
- const exactRoot = path.join(PLANS_DIR, `${input}.plan.md`);
57
- if (fs.existsSync(exactRoot)) {
58
- return exactRoot;
59
- }
60
- const plansRoot = fs.readdirSync(PLANS_DIR);
61
- const prefixMatch = plansRoot.find((n) => {
62
- if (!n.endsWith('.plan.md')) {
63
- return false;
64
- }
65
- const base = n.slice(0, -'.plan.md'.length);
66
- return base === input || base.startsWith(`${input}-`);
67
- });
68
- if (prefixMatch) {
69
- return path.join(PLANS_DIR, prefixMatch);
70
- }
71
- const nested = findIn(PLANS_DIR);
72
- if (nested) {
73
- return nested;
74
- }
75
- die(`Plan not found: ${input}`);
76
- }
77
-
78
- /**
79
- * @param {string} planPath
80
- * @param {string} key
81
- * @param {string} value
82
- */
83
- function patchPlanMeta(planPath, key, value) {
84
- const raw = fs.readFileSync(planPath, 'utf8');
85
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
86
- if (!match) {
87
- die('Plan has no frontmatter to patch');
88
- }
89
- let yaml = match[1];
90
- const quoted = JSON.stringify(String(value));
91
- const re = new RegExp(`^${key}:.*$`, 'm');
92
- if (re.test(yaml)) {
93
- yaml = yaml.replace(re, `${key}: ${quoted}`);
94
- } else {
95
- yaml = `${yaml}\n${key}: ${quoted}`;
96
- }
97
- fs.writeFileSync(planPath, `---\n${yaml}\n---\n${match[2]}`, 'utf8');
98
- }
99
-
100
- module.exports = {
101
- die,
102
- resolvePlanPath,
103
- patchPlanMeta
104
- };
@@ -1,162 +0,0 @@
1
- /**
2
- * @fileoverview Parse Cursor plan frontmatter (shared).
3
- */
4
-
5
- 'use strict';
6
-
7
- const { readFileSync } = require('../../lib/internal/fs-real-sync');
8
- const { REPO_ROOT, PLANS_DIR } = require('./plan-config');
9
-
10
- const TODO_STATUSES = new Set(['pending', 'in_progress', 'completed', 'cancelled']);
11
-
12
- /**
13
- * @param {string} block
14
- * @returns {Array<{ id: string, content: string, status: string }>}
15
- */
16
- function parseTodosBlock(block) {
17
- const todos = [];
18
- const chunks = block
19
- .trim()
20
- .split(/(?:^|\n)\s*-\s+id:\s*/)
21
- .filter(Boolean);
22
- for (const chunk of chunks) {
23
- const idLine = chunk.split(/\r?\n/)[0].trim();
24
- const contentMatch = chunk.match(/\n\s+content:\s*\|\s*\n([\s\S]*?)(?=\n\s+status:)/);
25
- const statusMatch = chunk.match(/\n\s+status:\s*(\w+)/);
26
- todos.push({
27
- id: idLine,
28
- content: contentMatch ? contentMatch[1].replace(/^\s{6}/gm, '').trim() : '',
29
- status: statusMatch ? statusMatch[1] : 'pending'
30
- });
31
- }
32
- return todos;
33
- }
34
-
35
- /**
36
- * @param {string} raw
37
- * @returns {Record<string, unknown> | null}
38
- */
39
- function parseSimpleYaml(raw) {
40
- const meta = {};
41
- const lines = raw.split(/\r?\n/);
42
- let i = 0;
43
-
44
- while (i < lines.length) {
45
- const line = lines[i];
46
- const keyMatch = line.match(/^([a-zA-Z0-9_]+):\s*(.*)$/);
47
- if (!keyMatch) {
48
- i += 1;
49
- continue;
50
- }
51
- const key = keyMatch[1];
52
- const rest = keyMatch[2].trim();
53
-
54
- if (key === 'todos' && rest === '') {
55
- const block = [];
56
- i += 1;
57
- while (i < lines.length && !/^[a-zA-Z0-9_]+:\s*/.test(lines[i])) {
58
- block.push(lines[i]);
59
- i += 1;
60
- }
61
- meta.todos = parseTodosBlock(block.join('\n'));
62
- continue;
63
- }
64
-
65
- if (rest === '|' || rest === '>') {
66
- const block = [];
67
- i += 1;
68
- while (i < lines.length && /^\s/.test(lines[i])) {
69
- block.push(lines[i].replace(/^\s{2}/, ''));
70
- i += 1;
71
- }
72
- meta[key] = block.join('\n').trimEnd();
73
- continue;
74
- }
75
-
76
- if (rest === '' && i + 1 < lines.length && lines[i + 1].trim().startsWith('- ')) {
77
- const items = [];
78
- i += 1;
79
- while (i < lines.length && lines[i].trim().startsWith('- ')) {
80
- items.push(lines[i].trim().slice(2).trim());
81
- i += 1;
82
- }
83
- meta[key] = items;
84
- continue;
85
- }
86
-
87
- if (rest.startsWith('"') && rest.endsWith('"')) {
88
- meta[key] = rest.slice(1, -1).replace(/\\"/g, '"');
89
- } else if (rest === 'true' || rest === 'false') {
90
- meta[key] = rest === 'true';
91
- } else if (/^\d+$/.test(rest)) {
92
- meta[key] = Number(rest);
93
- } else if (rest) {
94
- meta[key] = rest;
95
- } else {
96
- meta[key] = null;
97
- }
98
- i += 1;
99
- }
100
-
101
- return meta;
102
- }
103
-
104
- /**
105
- * @param {string} planPath
106
- * @returns {{ meta: Record<string, unknown>, body: string } | null}
107
- */
108
- function loadPlanFile(planPath) {
109
- const raw = readFileSync(planPath, 'utf8');
110
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
111
- if (!match) {
112
- return null;
113
- }
114
- const meta = parseSimpleYaml(match[1]);
115
- if (Array.isArray(meta.todos)) {
116
- meta.todos = normalizeTodos(meta.todos);
117
- }
118
- return { meta, body: match[2] };
119
- }
120
-
121
- /**
122
- * @param {unknown} todos
123
- * @returns {Array<{ id: string, content: string, status: string }>}
124
- */
125
- function normalizeTodos(todos) {
126
- if (!Array.isArray(todos)) {
127
- return [];
128
- }
129
- return todos
130
- .filter((t) => t && typeof t === 'object')
131
- .map((t) => {
132
- const item = /** @type {{ id?: string, content?: string, status?: string }} */ (t);
133
- let status = String(item.status || 'pending');
134
- if (!TODO_STATUSES.has(status)) {
135
- status = 'pending';
136
- }
137
- return {
138
- id: String(item.id || ''),
139
- content: String(item.content || ''),
140
- status
141
- };
142
- })
143
- .filter((t) => t.id);
144
- }
145
-
146
- /**
147
- * @param {Record<string, unknown>} meta
148
- * @param {string} surface
149
- * @returns {boolean}
150
- */
151
- function hasSurface(meta, surface) {
152
- const surfaces = meta.surfaces;
153
- return Array.isArray(surfaces) && surfaces.includes(surface);
154
- }
155
-
156
- module.exports = {
157
- REPO_ROOT,
158
- PLANS_DIR,
159
- loadPlanFile,
160
- normalizeTodos,
161
- hasSurface
162
- };
@@ -1,337 +0,0 @@
1
- /**
2
- * @fileoverview Pre-implementation plan validation gates (/validate-plan).
3
- */
4
-
5
- /* eslint-disable valid-jsdoc, require-jsdoc */
6
-
7
- 'use strict';
8
-
9
- const {
10
- statSync,
11
- mkdirSync,
12
- writeFileSync
13
- } = require('../../lib/internal/fs-real-sync');
14
- const path = require('path');
15
- const { loadPlanFile, hasSurface } = require('./plan-parse');
16
-
17
- const REPO_ROOT = path.resolve(__dirname, '../..');
18
- const PREVALIDATE_REPORT_DIR = path.join(REPO_ROOT, 'temp/plan-prevalidation');
19
-
20
- /**
21
- * @param {unknown} value
22
- * @returns {string}
23
- */
24
- function escapeMarkdownCell(value) {
25
- return String(value ?? '')
26
- .replace(/\r?\n+/g, ' ')
27
- .replace(/\|/g, '\\|')
28
- .trim();
29
- }
30
-
31
- /**
32
- * @typedef {{ label: string, status: 'pass' | 'fail' | 'warn' | 'skip', detail?: string }} GateResult
33
- */
34
-
35
- /**
36
- * @param {string} label
37
- * @param {'pass' | 'fail' | 'warn' | 'skip'} status
38
- * @param {string} [detail]
39
- * @returns {GateResult}
40
- */
41
- function gate(label, status, detail) {
42
- return { label, status, detail };
43
- }
44
-
45
- /**
46
- * @param {string} body
47
- * @param {string} heading
48
- * @returns {boolean}
49
- */
50
- function hasHeading(body, heading) {
51
- const re = new RegExp(`^## ${heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*$`, 'm');
52
- return re.test(body);
53
- }
54
-
55
- /**
56
- * @param {string} body
57
- * @returns {string | null}
58
- */
59
- function extractReportStatus(body) {
60
- const reportMatch = body.match(/## Plan Validation Report[\s\S]*?\*\*Status\*\*:\s*(.+)/i);
61
- if (!reportMatch) {
62
- return null;
63
- }
64
- const line = reportMatch[1].trim();
65
- if (/VALIDATED/i.test(line) && !/NEEDS|INCOMPLETE/i.test(line)) {
66
- return 'validated';
67
- }
68
- if (/NEEDS UPDATES/i.test(line)) {
69
- return 'needs-updates';
70
- }
71
- if (/INCOMPLETE/i.test(line)) {
72
- return 'incomplete';
73
- }
74
- return null;
75
- }
76
-
77
- /**
78
- * @param {string} body
79
- * @param {string} subsection
80
- * @returns {boolean}
81
- */
82
- function hasReportSubsection(body, subsection) {
83
- const reportIdx = body.indexOf('## Plan Validation Report');
84
- if (reportIdx < 0) {
85
- return false;
86
- }
87
- const report = body.slice(reportIdx);
88
- const re = new RegExp(`^### ${subsection.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'm');
89
- return re.test(report);
90
- }
91
-
92
- /**
93
- * @param {string} planPath
94
- * @param {string | undefined} lastValidatedAt
95
- * @returns {boolean}
96
- */
97
- function isValidationStale(planPath, lastValidatedAt) {
98
- if (!lastValidatedAt) {
99
- return false;
100
- }
101
- const validatedMs = Date.parse(String(lastValidatedAt));
102
- if (Number.isNaN(validatedMs)) {
103
- return false;
104
- }
105
- const mtime = statSync(planPath).mtimeMs;
106
- return mtime > validatedMs + 86400000;
107
- }
108
-
109
- /**
110
- * @param {string} body
111
- * @returns {boolean}
112
- */
113
- function needsManualSection(body, meta) {
114
- const reportIdx = body.indexOf('## Plan Validation Report');
115
- const planBody = reportIdx >= 0 ? body.slice(0, reportIdx) : body;
116
- const todos = Array.isArray(meta.todos) ? meta.todos : [];
117
- const todoText = todos.map((t) => `${t.content} ${t.id}`).join(' ');
118
- const combined = `${planBody}\n${todoText}`;
119
- return /manual|pilot|live proof|e2e smoke|container proof|dev-reload/i.test(combined);
120
- }
121
-
122
- /**
123
- * @param {Record<string, unknown>} meta
124
- * @returns {boolean}
125
- */
126
- function isCodePlan(meta) {
127
- return meta.planType === 'code';
128
- }
129
-
130
- /**
131
- * @param {Record<string, unknown>} meta
132
- * @returns {boolean}
133
- */
134
- function isMetaPlan(meta) {
135
- return meta.planType === 'meta';
136
- }
137
-
138
- /**
139
- * @param {string} planPath
140
- * @param {{ allowNeedsUpdates?: boolean, repoProfile?: string }} [opts]
141
- */
142
- function runPrevalidateGates(planPath, opts = {}) {
143
- const loaded = loadPlanFile(planPath);
144
- if (!loaded) {
145
- return {
146
- gates: [gate('plan-parse', 'fail', 'invalid or missing frontmatter')],
147
- reportStatus: 'incomplete',
148
- planValidatedStatus: 'incomplete',
149
- meta: {},
150
- body: ''
151
- };
152
- }
153
-
154
- const { meta, body } = loaded;
155
- const gates = [];
156
- const code = isCodePlan(meta);
157
- const metaPlan = isMetaPlan(meta);
158
-
159
- if (!meta.name && !meta.overview) {
160
- gates.push(gate('frontmatter-name', 'fail', 'name or overview required'));
161
- } else {
162
- gates.push(gate('frontmatter-name', 'pass'));
163
- }
164
-
165
- if (!Array.isArray(meta.todos) || meta.todos.length === 0) {
166
- gates.push(gate('todos', 'fail', 'frontmatter todos required'));
167
- } else {
168
- gates.push(gate('todos', 'pass', `${meta.todos.length} todo(s)`));
169
- }
170
-
171
- const scopeSections = ['Scope', 'Rules and Standards', 'Before Development', 'Definition of Done'];
172
- for (const section of scopeSections) {
173
- if (!hasHeading(body, section)) {
174
- gates.push(gate(`section-${section}`, 'fail', `missing ## ${section}`));
175
- } else {
176
- gates.push(gate(`section-${section}`, 'pass'));
177
- }
178
- }
179
-
180
- if (hasHeading(body, 'Scope')) {
181
- const scopeOk =
182
- /### In scope/i.test(body) && /### Out of scope/i.test(body);
183
- gates.push(
184
- scopeOk
185
- ? gate('scope-boundaries', 'pass')
186
- : gate('scope-boundaries', 'fail', 'Scope needs ### In scope and ### Out of scope')
187
- );
188
- }
189
-
190
- if (code) {
191
- for (const section of ['Architecture alignment', 'Code reuse scan', 'Expected Automated Tests']) {
192
- gates.push(
193
- hasHeading(body, section)
194
- ? gate(`section-${section}`, 'pass')
195
- : gate(`section-${section}`, 'fail', `code plan missing ## ${section}`)
196
- );
197
- }
198
- if (!hasHeading(body, 'File Size Reduction Candidates')) {
199
- gates.push(gate('file-size-section', 'warn', 'missing ## File Size Reduction Candidates'));
200
- } else {
201
- gates.push(gate('file-size-section', 'pass'));
202
- }
203
- if (hasSurface(meta, 'api') && !meta.apiTiers) {
204
- gates.push(gate('api-tiers', 'fail', 'api surface requires apiTiers in frontmatter'));
205
- } else if (hasSurface(meta, 'api')) {
206
- gates.push(gate('api-tiers', 'pass'));
207
- } else {
208
- gates.push(gate('api-tiers', 'skip', 'no api surface'));
209
- }
210
- } else if (metaPlan) {
211
- gates.push(gate('architecture-section', 'skip', 'meta plan'));
212
- gates.push(gate('reuse-section', 'skip', 'meta plan'));
213
- gates.push(gate('automated-tests-section', 'skip', 'meta plan'));
214
- } else {
215
- gates.push(gate('code-sections', 'skip', `planType=${meta.planType || 'unknown'}`));
216
- }
217
-
218
- if (code && needsManualSection(body, meta) && !hasHeading(body, 'Manual testing')) {
219
- gates.push(gate('manual-testing-section', 'fail', 'manual scope detected — add ## Manual testing'));
220
- } else if (metaPlan) {
221
- gates.push(gate('manual-testing-section', 'skip', 'meta plan'));
222
- } else if (needsManualSection(body, meta)) {
223
- gates.push(gate('manual-testing-section', 'pass'));
224
- } else {
225
- gates.push(gate('manual-testing-section', 'skip', 'no manual scope'));
226
- }
227
-
228
- if (!hasHeading(body, 'Plan Validation Report')) {
229
- gates.push(gate('validation-report', 'fail', 'missing ## Plan Validation Report'));
230
- } else {
231
- gates.push(gate('validation-report', 'pass'));
232
- const reportStatus = extractReportStatus(body);
233
- if (!reportStatus) {
234
- gates.push(gate('report-status', 'fail', 'report missing **Status**: VALIDATED line'));
235
- } else if (reportStatus === 'validated') {
236
- gates.push(gate('report-status', 'pass', 'VALIDATED'));
237
- } else if (reportStatus === 'needs-updates') {
238
- gates.push(
239
- gate('report-status', opts.allowNeedsUpdates ? 'warn' : 'fail', 'NEEDS UPDATES')
240
- );
241
- } else {
242
- gates.push(gate('report-status', 'fail', 'INCOMPLETE'));
243
- }
244
-
245
- if (code) {
246
- for (const sub of [
247
- 'Architecture alignment',
248
- 'Code reuse scan',
249
- 'Testing strategy',
250
- 'Manual testing and approval'
251
- ]) {
252
- gates.push(
253
- hasReportSubsection(body, sub)
254
- ? gate(`report-${sub}`, 'pass')
255
- : gate(`report-${sub}`, 'fail', `report missing ### ${sub}`)
256
- );
257
- }
258
- }
259
- }
260
-
261
- if (isValidationStale(planPath, meta.lastValidatedAt)) {
262
- gates.push(gate('staleness', 'fail', 'plan modified after lastValidatedAt — re-validate'));
263
- } else if (meta.lastValidatedAt) {
264
- gates.push(gate('staleness', 'pass'));
265
- } else {
266
- gates.push(gate('staleness', 'skip', 'not yet validated'));
267
- }
268
-
269
- const hasFail = gates.some((g) => g.status === 'fail');
270
- const hasWarn = gates.some((g) => g.status === 'warn');
271
- let planValidatedStatus = 'validated';
272
- if (hasFail) {
273
- planValidatedStatus = 'incomplete';
274
- } else if (hasWarn) {
275
- planValidatedStatus = 'needs-updates';
276
- }
277
-
278
- const reportStatus = extractReportStatus(body) || planValidatedStatus;
279
- if (reportStatus !== 'validated') {
280
- planValidatedStatus = reportStatus === 'needs-updates' ? 'needs-updates' : 'incomplete';
281
- }
282
- if (hasFail) {
283
- planValidatedStatus = 'incomplete';
284
- }
285
-
286
- return { gates, reportStatus, planValidatedStatus, meta, body };
287
- }
288
-
289
- /**
290
- * @param {object} params
291
- * @returns {string}
292
- */
293
- function buildPrevalidateReport(params) {
294
- const { planPath, planName, actor, runAt, gates, planValidatedStatus, reportStatus } = params;
295
- const lines = [
296
- '# Plan pre-validation summary',
297
- '',
298
- '| Field | Value |',
299
- '| --- | --- |',
300
- `| **Plan** | \`${planName}\` |`,
301
- `| **Stamp status** | \`${planValidatedStatus}\` |`,
302
- `| **Report status** | \`${reportStatus || 'unknown'}\` |`,
303
- `| **Run at** | ${runAt} |`,
304
- `| **Validated by** | ${actor || '_not set_'} |`,
305
- `| **Plan file** | \`${path.relative(REPO_ROOT, planPath)}\` |`,
306
- '',
307
- '## Gates',
308
- '',
309
- '| Gate | Result | Detail |',
310
- '| --- | --- | --- |'
311
- ];
312
- for (const g of gates) {
313
- lines.push(`| ${escapeMarkdownCell(g.label)} | ${escapeMarkdownCell(g.status)} | ${escapeMarkdownCell(g.detail || '')} |`);
314
- }
315
- return `${lines.join('\n')}\n`;
316
- }
317
-
318
- /**
319
- * @param {string} content
320
- * @param {string} planBaseName
321
- */
322
- function writePrevalidateReport(content, planBaseName) {
323
- mkdirSync(PREVALIDATE_REPORT_DIR, { recursive: true });
324
- const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
325
- writeFileSync(path.join(PREVALIDATE_REPORT_DIR, `${planBaseName}.${stamp}.md`), content, 'utf8');
326
- writeFileSync(path.join(PREVALIDATE_REPORT_DIR, 'LATEST.md'), content, 'utf8');
327
- }
328
-
329
- module.exports = {
330
- PREVALIDATE_REPORT_DIR,
331
- runPrevalidateGates,
332
- buildPrevalidateReport,
333
- writePrevalidateReport,
334
- hasHeading,
335
- extractReportStatus,
336
- isValidationStale
337
- };