@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.
- package/lib/app/deploy-config.js +3 -2
- package/lib/channels/add-channel.js +11 -41
- package/lib/channels/approval-guides/index.js +3 -0
- package/lib/channels/approval-guides/microsoft-copilot.js +26 -17
- package/lib/channels/channel-add-existing.js +72 -0
- package/lib/channels/channel-runtime-resolver.js +5 -1
- package/lib/channels/channel-status-display.js +30 -3
- package/lib/channels/microsoft-copilot-entra-sso-form.js +73 -2
- package/lib/channels/microsoft-copilot-entra-sso.js +26 -5
- package/lib/channels/microsoft-copilot-limits.js +22 -3
- package/lib/channels/microsoft-copilot-package.js +59 -14
- package/lib/channels/microsoft-copilot-validate.js +40 -10
- package/lib/cli/attach-default-help-examples.js +102 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/setup-credential-deployment.js +7 -5
- package/lib/cli/setup-utility-validate.js +1 -0
- package/lib/commands/credential-push.js +37 -5
- package/lib/commands/package-score.js +72 -0
- package/lib/commands/role-assistant.js +46 -8
- package/lib/deployment/deployer-poll-run.js +2 -2
- package/lib/deployment/deployer.js +1 -1
- package/lib/deployment/environment.js +2 -2
- package/lib/external-system/deploy.js +11 -21
- package/lib/integration-definition/apply.js +12 -14
- package/lib/integration-definition/enrich-openapi-operations.js +9 -10
- package/lib/lab/package-parity-score.js +417 -0
- package/lib/programmatic/capability-registry.js +1 -0
- package/lib/programmatic/help-content/channel-onboarding.md +35 -0
- package/lib/programmatic/help-content/cip-overview.md +27 -0
- package/lib/programmatic/help-content/connected-system-ui.md +70 -0
- package/lib/programmatic/help-content/dimensions-guide.md +23 -0
- package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
- package/lib/programmatic/help-content/overview.md +25 -0
- package/lib/programmatic/help-content/subscription-guide.md +27 -0
- package/lib/programmatic/help-content/workflow.md +50 -0
- package/lib/programmatic/openapi-component-schemas.js +1 -0
- package/lib/programmatic/openapi-descriptions.js +6 -0
- package/lib/programmatic/openapi-envelope-examples.js +11 -0
- package/lib/programmatic/openapi-operation-bindings.js +5 -0
- package/lib/programmatic/openapi-request-schemas.js +39 -13
- package/lib/programmatic/openapi-result-schemas.js +30 -0
- package/lib/programmatic/openapi-schema-descriptions-core.js +18 -0
- package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
- package/lib/programmatic/persist-supplied-openapi.js +180 -0
- package/lib/programmatic/route-handler-map.js +6 -0
- package/lib/programmatic/run-integration-definition.js +13 -5
- package/lib/role-assistant/evidence-sync.js +55 -7
- package/lib/role-assistant/test-runner-attachments.js +118 -0
- package/lib/role-assistant/test-runner-bid-apply.js +100 -21
- package/lib/role-assistant/test-runner-bid-document.js +1 -1
- package/lib/role-assistant/test-runner-start-payload.js +96 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +11 -7
- package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
- package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
- package/lib/role-assistant/test-runner-workhub.js +68 -105
- package/lib/role-assistant/test-runner.js +5 -63
- package/lib/role-assistant/test-suite.js +33 -40
- package/lib/schema/external-datasource.schema.json +13 -4
- package/lib/utils/compose-generator.js +10 -1
- package/lib/utils/compose-traefik-extra-routes.js +107 -0
- package/lib/utils/docker-build.js +2 -2
- package/lib/utils/help-builder.js +2 -1
- package/lib/utils/promote-dev-docker-image.js +15 -10
- package/package.json +14 -16
- package/templates/README.md +12 -1
- package/templates/applications/dataplane/application.yaml +8 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +2 -1
- package/templates/applications/miso-controller/rbac.yaml +1 -0
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +6 -2
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +8 -5
- package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +74 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +29 -14
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +6 -2
- package/templates/python/docker-compose.hbs +36 -0
- package/templates/typescript/docker-compose.hbs +36 -0
- package/.cursor/plans/Archive/157.1-security-quality-violations-by-type.csv +0 -374
- package/.cursor/process/README.md +0 -9
- package/.cursor/rules/anchor-docs.mdc +0 -15
- package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
- package/.cursor/rules/cli-layout.mdc +0 -81
- package/.cursor/rules/cli-product-neutral.mdc +0 -43
- package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
- package/.cursor/rules/docs-rules.mdc +0 -32
- package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
- package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
- package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
- package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
- package/.cursor/rules/project-rules.mdc +0 -913
- package/.cursor/skills/README.md +0 -15
- package/.dockerignore +0 -52
- package/.eslintignore +0 -22
- package/.markdownlint.json +0 -14
- package/anchor-docs/README.md +0 -10
- package/anchor-docs/_TEMPLATE +0 -24
- package/babel.config.js +0 -6
- package/bin/builder-api-cleanup.js +0 -26
- package/bin/builder-api-nest.js +0 -10
- package/bin/builder-api-spike.js +0 -19
- package/enterprise-knowledge/README.md +0 -50
- package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
- package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
- package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
- package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
- package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
- package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
- package/integration/README.md +0 -81
- package/integration/github/README.md +0 -53
- package/integration/github/application.json +0 -21
- package/integration/github/deploy.js +0 -72
- package/integration/github/env.template +0 -11
- package/integration/github/github-datasource-issue-comments.json +0 -239
- package/integration/github/github-datasource-issues.json +0 -399
- package/integration/github/github-datasource-repository.json +0 -354
- package/integration/github/github-deploy.json +0 -1202
- package/integration/github/github-system.json +0 -82
- package/integration/hubspot-test/README.md +0 -159
- package/integration/hubspot-test/application.json +0 -54
- package/integration/hubspot-test/companies.json +0 -2048
- package/integration/hubspot-test/create-hubspot.js +0 -523
- package/integration/hubspot-test/env.template +0 -4
- package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
- package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
- package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
- package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
- package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
- package/integration/hubspot-test/hubspot-test-system.json +0 -74
- package/integration/hubspot-test/rbac.json +0 -166
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
- package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
- package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
- package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
- package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
- package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
- package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
- package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
- package/integration/hubspot-test/test-dataplane-down.js +0 -209
- package/integration/hubspot-test/test.js +0 -1588
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
- package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
- package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
- package/integration/roundtrip-test-local/README.md +0 -143
- package/integration/roundtrip-test-local/application.yaml +0 -13
- package/integration/roundtrip-test-local/env.template +0 -15
- package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
- package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
- package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
- package/integration/roundtrip-test-local2/README.md +0 -143
- package/integration/roundtrip-test-local2/application.yaml +0 -13
- package/integration/roundtrip-test-local2/env.template +0 -15
- package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
- package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
- package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
- package/integration/test/wizard.yaml +0 -8
- package/jest.config.coverage.js +0 -37
- package/jest.config.default.js +0 -11
- package/jest.config.integration.fixtures.js +0 -22
- package/jest.config.integration.js +0 -33
- package/jest.config.isolated.js +0 -11
- package/jest.config.manual.js +0 -30
- package/jest.global-live-fabrix-hooks.js +0 -36
- package/jest.isolated-projects.js +0 -283
- package/jest.projects.js +0 -79
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +0 -205
- package/packages/builder-api/README.md +0 -120
- package/packages/builder-api/package.json +0 -30
- package/packages/builder-api/src/app.module.ts +0 -7
- package/packages/builder-api/src/auth/auth.module.ts +0 -5
- package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
- package/packages/builder-api/src/main.ts +0 -16
- package/packages/builder-api/tsconfig.json +0 -15
- package/scripts/check-builder-api-architecture-grep.js +0 -79
- package/scripts/check-datasource-test-run-schema-sync.js +0 -34
- package/scripts/ci-fix.sh +0 -19
- package/scripts/ci-simulate.sh +0 -19
- package/scripts/diagnose-cli.js +0 -150
- package/scripts/docker_image_content_gate.js +0 -47
- package/scripts/docker_image_content_gate.sh +0 -5
- package/scripts/export-builder-api-evidence.js +0 -262
- package/scripts/install-local.js +0 -490
- package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
- package/scripts/lib/builder-api-docker-staging-core.js +0 -183
- package/scripts/lib/docker-image-content-gate-core.js +0 -166
- package/scripts/lib/plan-actor.js +0 -109
- package/scripts/lib/plan-config.js +0 -20
- package/scripts/lib/plan-gates.js +0 -154
- package/scripts/lib/plan-meta.js +0 -104
- package/scripts/lib/plan-parse.js +0 -162
- package/scripts/lib/plan-prevalidate-gates.js +0 -337
- package/scripts/lib/plan-run-core.js +0 -125
- package/scripts/lib/plan-run-report.js +0 -119
- package/scripts/plan-approve.js +0 -24
- package/scripts/plan-archive.js +0 -49
- package/scripts/plan-run.js +0 -13
- package/scripts/plan-set-actor.js +0 -21
- package/scripts/plan-validate.js +0 -80
- package/scripts/pnpm/af.mjs +0 -125
- package/scripts/pnpm/check-quiet.mjs +0 -32
- package/scripts/pnpm/dev-install.mjs +0 -29
- package/scripts/pnpm/dev-reload.mjs +0 -18
- package/scripts/pnpm/help.mjs +0 -55
- package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
- package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
- package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
- package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
- package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
- package/scripts/pnpm/lib/spinner.mjs +0 -42
- package/scripts/pnpm-global-remove.js +0 -48
- package/scripts/stage_builder_api_for_docker.js +0 -65
- package/scripts/sync-builder-api-template.js +0 -44
- package/scripts/test-dataplane-bootstrap.js +0 -331
package/scripts/lib/plan-meta.js
DELETED
|
@@ -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
|
-
};
|