@jstn-sdk/ma 0.1.13 → 0.14.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.
- package/.agents/plugins/marketplace.json +0 -0
- package/.codex/agents/Architect.toml +1 -0
- package/.codex/agents/Auditor.toml +1 -0
- package/.codex/agents/Builder.toml +1 -0
- package/.codex/agents/Flow.toml +1 -0
- package/.codex/agents/Sage.toml +1 -0
- package/.codex/agents/Vibe.toml +1 -0
- package/.codex/hooks.json +12 -2
- package/.codex/prompts/enforcement.md +0 -0
- package/.codex/prompts/onboarding.md +6 -1
- package/.codex/prompts/release-rules.md +0 -0
- package/.codex/prompts/skill-contract.md +0 -0
- package/COVERAGE.md +7 -7
- package/DEMO.md +15 -6
- package/LICENSE +0 -0
- package/README.md +126 -793
- package/bin/ma.js +585 -97
- package/data/clone-data.ledger.json +0 -0
- package/data/clone-data.proof.json +0 -0
- package/data/clone-data.rvf +0 -0
- package/docs/README.md +8 -2
- package/docs/autonomous-tasks.md +46 -0
- package/docs/codex-integration.md +16 -0
- package/docs/getting-started.md +28 -4
- package/docs/installed-sdk.md +0 -0
- package/docs/mcp-setup.md +30 -1
- package/docs/qa/{release-issue-gates-0.1.13.json → release-issue-gates-0.14.0.json} +77 -77
- package/docs/qa/release-readiness-0.1.5.md +0 -0
- package/docs/qa/{release-readiness-0.1.13.md → release-readiness-0.14.0.md} +12 -12
- package/docs/quality.md +24 -0
- package/docs/reference/native-engineering-patterns.md +0 -0
- package/docs/reference/native-security-playbooks.md +0 -0
- package/docs/reference/native-source-selection.md +0 -0
- package/docs/reference/native-style-and-deslop.md +0 -0
- package/docs/release-spec.md +27 -14
- package/docs/skills.md +1 -1
- package/index.js +73 -0
- package/mcp/collections.json +0 -0
- package/mcp/fallback.json +0 -0
- package/mcp/local/code-intel.js +179 -41
- package/mcp/local/context.js +180 -0
- package/mcp/local/memory.js +17 -6
- package/mcp/local/playbooks.js +0 -0
- package/mcp/local/quality.js +39 -0
- package/mcp/local/state.js +7 -3
- package/mcp/local/team-run.js +8 -3
- package/mcp/local/trace.js +0 -0
- package/mcp/local-capabilities.json +9 -0
- package/mcp/native-playbooks.json +0 -0
- package/mcp/servers.json +0 -0
- package/package.json +26 -4
- package/plugins/meta-architect/.app.json +1 -1
- package/plugins/meta-architect/.codex-plugin/plugin.json +1 -1
- package/plugins/meta-architect/.mcp.json +1 -1
- package/plugins/meta-architect/README.md +2 -2
- package/plugins/meta-architect/obsidian/main.js +137 -4
- package/plugins/meta-architect/obsidian/manifest.json +1 -1
- package/plugins/meta-architect/obsidian/styles.css +0 -0
- package/plugins/meta-architect/skills/align/SKILL.md +0 -0
- package/plugins/meta-architect/skills/align/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/align/references/shared-language.md +0 -0
- package/plugins/meta-architect/skills/arch/SKILL.md +0 -0
- package/plugins/meta-architect/skills/arch/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/build/SKILL.md +0 -0
- package/plugins/meta-architect/skills/build/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/SKILL.md +0 -0
- package/plugins/meta-architect/skills/cleanup/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/cleanup/references/style-and-deslop.md +0 -0
- package/plugins/meta-architect/skills/diagnose/SKILL.md +0 -0
- package/plugins/meta-architect/skills/diagnose/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/flow/SKILL.md +0 -0
- package/plugins/meta-architect/skills/flow/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/SKILL.md +0 -0
- package/plugins/meta-architect/skills/maestro/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/maestro/references/core-release-rules.md +0 -0
- package/plugins/meta-architect/skills/maestro/references/native-ingest-map.md +0 -0
- package/plugins/meta-architect/skills/sage/SKILL.md +0 -0
- package/plugins/meta-architect/skills/sage/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/sage/references/source-selection.md +0 -0
- package/plugins/meta-architect/skills/tdd/SKILL.md +0 -0
- package/plugins/meta-architect/skills/tdd/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vet/agents/openai.yaml +0 -0
- package/plugins/meta-architect/skills/vet/references/security-playbooks.md +0 -0
- package/plugins/meta-architect/skills/vibe/SKILL.md +0 -0
- package/plugins/meta-architect/skills/vibe/agents/openai.yaml +0 -0
- package/schemas/autonomous-task-queue.schema.json +56 -0
- package/schemas/handoff-packet.schema.json +36 -0
- package/schemas/setup-receipt.schema.json +28 -0
- package/schemas/skill-frontmatter.schema.json +20 -0
- package/schemas/support-bundle.schema.json +37 -0
- package/schemas/task-contract.schema.json +31 -0
- package/scripts/active-autonomy-hook.mjs +141 -11
- package/scripts/biome-staged.js +0 -0
- package/scripts/build-linux-packages.mjs +7 -3
- package/scripts/cleanup-test-fixtures.sh +55 -0
- package/scripts/context-hydration-hook.mjs +89 -0
- package/scripts/demo-smoke.js +285 -0
- package/scripts/doctor.js +10 -8
- package/scripts/install.sh +33 -4
- package/scripts/install.sh.sha256 +1 -0
- package/scripts/linux-package-lib.mjs +0 -0
- package/scripts/linux-package-smoke.mjs +2 -2
- package/scripts/package-size-check.mjs +26 -0
- package/scripts/plugin-sync.js +83 -17
- package/scripts/postinstall.js +17 -10
- package/scripts/prepack.js +9 -0
- package/scripts/ralph/prompt.md +0 -0
- package/scripts/release-metadata.js +0 -0
- package/scripts/release-sync.js +72 -7
- package/scripts/release-verify.js +100 -7
- package/scripts/setup-npmrc.js +48 -23
- package/scripts/skills-install.js +9 -2
- package/scripts/skills-manifest.js +2 -21
- package/scripts/skills-pack.js +2 -2
- package/scripts/skills-validate.js +5 -55
- package/skills/align/SKILL.md +0 -0
- package/skills/align/agents/openai.yaml +0 -0
- package/skills/align/references/shared-language.md +0 -0
- package/skills/arch/SKILL.md +0 -0
- package/skills/arch/agents/openai.yaml +0 -0
- package/skills/build/SKILL.md +0 -0
- package/skills/build/agents/openai.yaml +0 -0
- package/skills/cleanup/SKILL.md +0 -0
- package/skills/cleanup/agents/openai.yaml +0 -0
- package/skills/cleanup/references/style-and-deslop.md +0 -0
- package/skills/diagnose/SKILL.md +0 -0
- package/skills/diagnose/agents/openai.yaml +0 -0
- package/skills/flow/SKILL.md +0 -0
- package/skills/flow/agents/openai.yaml +0 -0
- package/skills/index.json +0 -0
- package/skills/maestro/SKILL.md +0 -0
- package/skills/maestro/agents/openai.yaml +0 -0
- package/skills/maestro/references/core-release-rules.md +0 -0
- package/skills/maestro/references/native-ingest-map.md +0 -0
- package/skills/sage/SKILL.md +0 -0
- package/skills/sage/agents/openai.yaml +0 -0
- package/skills/sage/references/source-selection.md +0 -0
- package/skills/tdd/SKILL.md +0 -0
- package/skills/tdd/agents/openai.yaml +0 -0
- package/skills/vet/SKILL.md +0 -0
- package/skills/vet/agents/openai.yaml +0 -0
- package/skills/vet/references/security-playbooks.md +0 -0
- package/skills/vibe/SKILL.md +0 -0
- package/skills/vibe/agents/openai.yaml +0 -0
- package/sprint/00-idea.md +0 -0
- package/sprint/01-architecture.md +0 -0
- package/sprint/02-oss-evidence.md +0 -0
- package/sprint/03-logic.md +0 -0
- package/sprint/04-security.md +0 -0
- package/sprint/05-dx-ux.md +0 -0
- package/sprint/06-build-plan.md +0 -0
- package/sprint/07-release.md +0 -0
- package/src/agents.js +279 -0
- package/src/bootstrap.js +252 -63
- package/src/build-gate.js +0 -0
- package/src/codex-app-server.js +291 -0
- package/src/decision-log.js +0 -0
- package/src/fs-utils.js +175 -4
- package/src/launcher.js +24 -21
- package/src/mcp-config.js +9 -1
- package/src/mcp-live-client.js +140 -7
- package/src/paths.js +6 -1
- package/src/policy.js +2 -2
- package/src/prelaunch.js +189 -0
- package/src/quality/ai-quality-orchestrator.js +328 -0
- package/src/release-issue-gates.js +62 -0
- package/src/release-operations.js +62 -0
- package/src/release-state.js +0 -0
- package/src/runtime/active-autonomy-core.js +0 -0
- package/src/runtime/agent-compat.js +31 -0
- package/src/runtime/alignment-sentinel.js +0 -0
- package/src/runtime/architect-review.js +248 -10
- package/src/runtime/autonomous-tasks.js +408 -0
- package/src/runtime/build-readiness.js +0 -0
- package/src/runtime/code-graph-rehearse.js +0 -0
- package/src/runtime/codeburn-core.js +112 -0
- package/src/runtime/context-authority.js +40 -0
- package/src/runtime/context-economy-core.js +40 -0
- package/src/runtime/continuity-graph.js +201 -0
- package/src/runtime/continuity-notes.js +71 -26
- package/src/runtime/core-source-ingest.js +0 -0
- package/src/runtime/detached-provider.js +82 -14
- package/src/runtime/doctor-report.js +18 -0
- package/src/runtime/environment-awareness-core.js +0 -0
- package/src/runtime/exposure-catalog.js +0 -0
- package/src/runtime/graphify-core.js +167 -0
- package/src/runtime/guidance-stack.js +9 -2
- package/src/runtime/handoff-packets.js +92 -0
- package/src/runtime/headroom-core.js +104 -0
- package/src/runtime/helper-orchestration-core.js +0 -0
- package/src/runtime/hook-profiles.js +20 -0
- package/src/runtime/learning-loop-core.js +110 -4
- package/src/runtime/live-agent-verification.js +122 -0
- package/src/runtime/maestro-events.js +0 -0
- package/src/runtime/maestro-manager.js +0 -0
- package/src/runtime/maestro-output.js +58 -0
- package/src/runtime/maestro-state.js +0 -0
- package/src/runtime/managed-markdown.js +29 -0
- package/src/runtime/mcp-authority.js +147 -0
- package/src/runtime/mcp-policy.js +0 -0
- package/src/runtime/obsidian-integration-core.js +255 -17
- package/src/runtime/obsidian-plugin-bridge.js +344 -38
- package/src/runtime/orchestrator.js +0 -0
- package/src/runtime/pi-maestro-core.js +89 -0
- package/src/runtime/preferences.js +79 -0
- package/src/runtime/project-context.js +636 -0
- package/src/runtime/prompt-strategy-core.js +0 -0
- package/src/runtime/quorum-review.js +0 -0
- package/src/runtime/ralph-execution-core.js +0 -0
- package/src/runtime/redaction-gateway.js +218 -19
- package/src/runtime/runtime-state.js +94 -2
- package/src/runtime/schema-migrations.js +162 -0
- package/src/runtime/semantic-recording-core.js +0 -0
- package/src/runtime/signal-hooks.js +13 -0
- package/src/runtime/skills-registry-export.js +92 -8
- package/src/runtime/startup-path.js +0 -0
- package/src/runtime/task-contracts.js +83 -0
- package/src/runtime/universal-plugin-broker-core.js +373 -50
- package/src/runtime/workspace-intelligence-runtime.js +0 -0
- package/src/runtime/workspace-virtualizer.js +0 -0
- package/src/runtime/workspaces.js +0 -0
- package/src/runtime-artifacts.js +0 -0
- package/src/setup-lifecycle.js +359 -0
- package/src/skill-frontmatter.js +78 -0
- package/src/skill-installer.js +226 -71
- package/src/skills.js +297 -19
- package/src/state-sync.js +0 -0
- package/src/test-fixtures.js +543 -0
- package/src/tui/grid.js +67 -0
- package/src/tui/status-grid.js +23 -0
- package/support-bundle.json +114 -0
- package/templates/AGENTS.md +6 -2
- package/templates/catalog-manifest.json +0 -0
- package/templates/model-instructions/core.md +0 -0
- package/templates/model-instructions/release.md +0 -0
- package/templates/model-instructions/security.md +0 -0
- package/templates/quality/ai-quality-rules.yml +37 -0
- package/docs/assets/image/Screenshot(1).png +0 -0
- package/docs/assets/image/Screenshot(2).png +0 -0
- package/docs/assets/image/Screenshot(3).png +0 -0
- package/docs/assets/image/Screenshot(4).png +0 -0
- package/docs/assets/image/Screenshot(5).png +0 -0
- package/docs/assets/image/Screenshot(6).png +0 -0
- package/docs/assets/image/Screenshot(7).png +0 -0
- package/docs/assets/image/Screenshot(8).png +0 -0
- package/docs/assets/image/Screenshot(9).png +0 -0
- package/docs/assets/meta-architect-logo.png +0 -0
- package/docs/assets/meta-architect-logo.svg +0 -8
- package/docs/onboarding.md +0 -81
- package/docs/prompt-guidance-contract.md +0 -17
- package/docs/prompt-guidance-fragments/active-autonomy-core.md +0 -7
- package/docs/skills-publishing.md +0 -255
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { writeJson } from "../fs-utils.js";
|
|
6
|
+
import { packageRoot } from "../paths.js";
|
|
7
|
+
|
|
8
|
+
const QUALITY_ROOT = path.join(".ma", "quality");
|
|
9
|
+
let appendQueue = Promise.resolve();
|
|
10
|
+
|
|
11
|
+
function violation(rule, severity, message, context, fix) {
|
|
12
|
+
return {
|
|
13
|
+
rule,
|
|
14
|
+
severity,
|
|
15
|
+
message,
|
|
16
|
+
...(context?.filePath ? { location: { file: context.filePath, line: 1, column: 1 } } : {}),
|
|
17
|
+
...(fix ? { fix } : {}),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function staticViolations(code, context) {
|
|
22
|
+
const findings = [];
|
|
23
|
+
if (/\bfetch\s*\(/.test(code) && !/AbortController|\btimeout\b|setTimeout/.test(code))
|
|
24
|
+
findings.push(
|
|
25
|
+
violation(
|
|
26
|
+
"network-calls-must-have-timeouts",
|
|
27
|
+
"critical",
|
|
28
|
+
"Network calls must use an abort or timeout boundary.",
|
|
29
|
+
context,
|
|
30
|
+
"Use AbortController with a bounded timeout.",
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
if (/\b(?:eval|Function)\s*\(/.test(code))
|
|
34
|
+
findings.push(
|
|
35
|
+
violation(
|
|
36
|
+
"no-dynamic-code-execution",
|
|
37
|
+
"critical",
|
|
38
|
+
"Dynamic code execution is forbidden.",
|
|
39
|
+
context,
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
if (/:\s*any\b/.test(code))
|
|
43
|
+
findings.push(
|
|
44
|
+
violation(
|
|
45
|
+
"no-any-types",
|
|
46
|
+
"error",
|
|
47
|
+
"TypeScript any is forbidden in generated code.",
|
|
48
|
+
context,
|
|
49
|
+
"Use a specific type or unknown with a type guard.",
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
if (/\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9_]{16,}|AIza[0-9A-Za-z-_]{20,})\b/.test(code))
|
|
53
|
+
findings.push(
|
|
54
|
+
violation(
|
|
55
|
+
"no-hardcoded-secrets",
|
|
56
|
+
"critical",
|
|
57
|
+
"A credential-shaped value was found in generated code.",
|
|
58
|
+
context,
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
if (
|
|
62
|
+
(context?.filePath?.includes("/controllers/") || context?.filePath?.includes("/routes/")) &&
|
|
63
|
+
/db\.collection|sequelize\.query|prisma\.\w+/.test(code)
|
|
64
|
+
)
|
|
65
|
+
findings.push(
|
|
66
|
+
violation(
|
|
67
|
+
"no-direct-db-access-from-controllers",
|
|
68
|
+
"error",
|
|
69
|
+
"Controllers must use a service boundary for database access.",
|
|
70
|
+
context,
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
return findings;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function calculateQualityScore(kpis) {
|
|
77
|
+
const deletion = Math.min(
|
|
78
|
+
(Math.max(0, kpis.linesDeleted) / Math.max(kpis.linesAdded, 1)) * 30,
|
|
79
|
+
30,
|
|
80
|
+
);
|
|
81
|
+
const stability = 30 / (1 + Math.abs(kpis.p95LatencyDelta) / 100);
|
|
82
|
+
const security =
|
|
83
|
+
kpis.securityIssuesFound === 0
|
|
84
|
+
? 20
|
|
85
|
+
: Math.min(kpis.securityIssuesFixed / kpis.securityIssuesFound, 1) * 20;
|
|
86
|
+
const coverage = Math.max(0, Math.min(kpis.testCoverage, 1)) * 20;
|
|
87
|
+
const cost = kpis.cloudCostDelta <= 0 ? 10 : Math.max(0, 10 - kpis.cloudCostDelta * 10);
|
|
88
|
+
return (
|
|
89
|
+
Math.round(
|
|
90
|
+
Math.max(0, Math.min(100, deletion + stability + security + coverage + cost)) * 100,
|
|
91
|
+
) / 100
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function runCommand(command, args, options = {}) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const child = spawn(command, args, {
|
|
98
|
+
cwd: options.cwd,
|
|
99
|
+
shell: false,
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
});
|
|
102
|
+
let stdout = "";
|
|
103
|
+
let stderr = "";
|
|
104
|
+
const timer = setTimeout(() => {
|
|
105
|
+
child.kill("SIGTERM");
|
|
106
|
+
reject(new Error(`${command} timed out`));
|
|
107
|
+
}, options.timeoutMs ?? 30_000);
|
|
108
|
+
child.stdout.on("data", (chunk) => {
|
|
109
|
+
stdout += chunk;
|
|
110
|
+
});
|
|
111
|
+
child.stderr.on("data", (chunk) => {
|
|
112
|
+
stderr += chunk;
|
|
113
|
+
});
|
|
114
|
+
child.on("error", (error) => {
|
|
115
|
+
clearTimeout(timer);
|
|
116
|
+
reject(error);
|
|
117
|
+
});
|
|
118
|
+
child.on("close", (code) => {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
resolve({ code: code ?? 1, stdout: stdout.toString(), stderr: stderr.toString() });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class AIQualityOrchestrator {
|
|
126
|
+
constructor(config = {}) {
|
|
127
|
+
this.config = { projectRoot: process.cwd(), requireTests: true, ...config };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async validate(code, context = {}) {
|
|
131
|
+
if (typeof code !== "string" || !code.trim())
|
|
132
|
+
throw new Error("Quality validation requires generated code");
|
|
133
|
+
const violations = staticViolations(code, context);
|
|
134
|
+
const semgrep = await this.runSemgrep(code, context);
|
|
135
|
+
violations.push(...semgrep.violations);
|
|
136
|
+
const architecture = await this.runArchUnit(code, context);
|
|
137
|
+
violations.push(...(architecture.violations ?? []));
|
|
138
|
+
if (this.config.requireTests && !context.testEvidence?.approved)
|
|
139
|
+
violations.push(
|
|
140
|
+
violation(
|
|
141
|
+
"tests-before-code",
|
|
142
|
+
"critical",
|
|
143
|
+
"Generated code has no approved test evidence.",
|
|
144
|
+
context,
|
|
145
|
+
"Provide approved tests before accepting generated code.",
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
const kpis = {
|
|
149
|
+
linesDeleted: 0,
|
|
150
|
+
linesAdded: code.split("\n").length,
|
|
151
|
+
cloudCostDelta: 0,
|
|
152
|
+
p95LatencyDelta: 0,
|
|
153
|
+
testCoverage: Number(context.testCoverage ?? 0),
|
|
154
|
+
securityIssuesFound: violations.filter((v) => v.severity === "critical").length,
|
|
155
|
+
securityIssuesFixed: 0,
|
|
156
|
+
qualityScore: 0,
|
|
157
|
+
};
|
|
158
|
+
kpis.qualityScore = calculateQualityScore(kpis);
|
|
159
|
+
const blocked =
|
|
160
|
+
violations.some((item) => item.severity === "critical" || item.severity === "error") ||
|
|
161
|
+
kpis.qualityScore < (this.config.qualityScoreThreshold ?? 60);
|
|
162
|
+
const result = {
|
|
163
|
+
passed: !blocked,
|
|
164
|
+
score: kpis.qualityScore,
|
|
165
|
+
violations,
|
|
166
|
+
kpis,
|
|
167
|
+
reviewRequired: blocked,
|
|
168
|
+
...(blocked ? { reviewReason: "One or more mandatory quality gates failed." } : {}),
|
|
169
|
+
};
|
|
170
|
+
await this.#append("violations.json", {
|
|
171
|
+
schemaVersion: "0.1.0",
|
|
172
|
+
record_type: "ai_quality_violations",
|
|
173
|
+
recordedAt: new Date().toISOString(),
|
|
174
|
+
violations,
|
|
175
|
+
});
|
|
176
|
+
await this.#append("kpis.json", {
|
|
177
|
+
schemaVersion: "0.1.0",
|
|
178
|
+
record_type: "ai_quality_kpis",
|
|
179
|
+
recordedAt: new Date().toISOString(),
|
|
180
|
+
kpis,
|
|
181
|
+
});
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async runSemgrep(code, context = {}) {
|
|
186
|
+
if (typeof this.config.semgrepRunner === "function")
|
|
187
|
+
return this.config.semgrepRunner(code, context);
|
|
188
|
+
const root = context.projectRoot ?? this.config.projectRoot;
|
|
189
|
+
const projectRules = path.join(root, ".ma", "semgrep", "ai-quality-rules.yml");
|
|
190
|
+
let rules = context.rulesPath ?? projectRules;
|
|
191
|
+
if (!context.rulesPath) {
|
|
192
|
+
try {
|
|
193
|
+
await fs.access(projectRules);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error.code === "ENOENT") {
|
|
196
|
+
rules = path.join(packageRoot, "templates", "quality", "ai-quality-rules.yml");
|
|
197
|
+
} else {
|
|
198
|
+
return {
|
|
199
|
+
passed: false,
|
|
200
|
+
blocked: true,
|
|
201
|
+
available: false,
|
|
202
|
+
violations: [
|
|
203
|
+
violation(
|
|
204
|
+
"semgrep-rules-error",
|
|
205
|
+
"critical",
|
|
206
|
+
`Semgrep rules could not be read: ${error.message}`,
|
|
207
|
+
context,
|
|
208
|
+
),
|
|
209
|
+
],
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const input = path.join(root, ".ma", "quality", `.scan-${randomUUID()}.ts`);
|
|
215
|
+
await fs.mkdir(path.dirname(input), { recursive: true });
|
|
216
|
+
await fs.writeFile(input, code, { mode: 0o600 });
|
|
217
|
+
try {
|
|
218
|
+
const result = await runCommand("semgrep", ["--config", rules, "--json", "--quiet", input], {
|
|
219
|
+
cwd: root,
|
|
220
|
+
timeoutMs: this.config.timeoutMs,
|
|
221
|
+
});
|
|
222
|
+
if (result.code === 0)
|
|
223
|
+
return { passed: true, blocked: false, available: true, violations: [] };
|
|
224
|
+
if (result.code === 1) {
|
|
225
|
+
const parsed = JSON.parse(result.stdout || "{}");
|
|
226
|
+
return {
|
|
227
|
+
passed: false,
|
|
228
|
+
blocked: true,
|
|
229
|
+
available: true,
|
|
230
|
+
violations: (parsed.results ?? []).map((item) =>
|
|
231
|
+
violation(
|
|
232
|
+
item.check_id,
|
|
233
|
+
"error",
|
|
234
|
+
item.extra?.message ?? "Semgrep rule failed",
|
|
235
|
+
context,
|
|
236
|
+
item.extra?.fix,
|
|
237
|
+
),
|
|
238
|
+
),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
passed: false,
|
|
243
|
+
blocked: true,
|
|
244
|
+
available: true,
|
|
245
|
+
violations: [
|
|
246
|
+
violation(
|
|
247
|
+
"semgrep-error",
|
|
248
|
+
"critical",
|
|
249
|
+
result.stderr.trim() || "Semgrep failed to execute",
|
|
250
|
+
context,
|
|
251
|
+
),
|
|
252
|
+
],
|
|
253
|
+
};
|
|
254
|
+
} catch (error) {
|
|
255
|
+
return {
|
|
256
|
+
passed: false,
|
|
257
|
+
blocked: true,
|
|
258
|
+
available: false,
|
|
259
|
+
violations: [
|
|
260
|
+
violation(
|
|
261
|
+
"semgrep-unavailable",
|
|
262
|
+
"critical",
|
|
263
|
+
`Semgrep is unavailable: ${error.message}`,
|
|
264
|
+
context,
|
|
265
|
+
),
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
} finally {
|
|
269
|
+
await fs.rm(input, { force: true });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async runArchUnit(code, context = {}) {
|
|
274
|
+
const violations = staticViolations(code, context).filter(
|
|
275
|
+
(item) => item.rule === "no-direct-db-access-from-controllers",
|
|
276
|
+
);
|
|
277
|
+
return { passed: violations.length === 0, blocked: violations.length > 0, violations };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async testDrivenGeneration(
|
|
281
|
+
requirement,
|
|
282
|
+
{ generateTests, generateCode, approveTests, runTests } = {},
|
|
283
|
+
) {
|
|
284
|
+
if (
|
|
285
|
+
![generateTests, generateCode, approveTests, runTests].every((fn) => typeof fn === "function")
|
|
286
|
+
)
|
|
287
|
+
throw new Error(
|
|
288
|
+
"Test-driven generation requires explicit test, approval, code, and runner functions",
|
|
289
|
+
);
|
|
290
|
+
const tests = await generateTests(requirement);
|
|
291
|
+
if (!(await approveTests(tests))) throw new Error("Tests were not approved");
|
|
292
|
+
const code = await generateCode(requirement, tests);
|
|
293
|
+
const result = await runTests(tests, code);
|
|
294
|
+
if (!result?.allPassed || Number(result.coverage ?? 0) < 0.8)
|
|
295
|
+
throw new Error("Approved tests did not pass with 80% coverage");
|
|
296
|
+
return { tests, code };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async generateCursorRules(projectRoot) {
|
|
300
|
+
const content = `# AI Engineering Quality Standards\n\n## Enforced\n- Network calls require bounded AbortController timeouts.\n- Dynamic code execution and hardcoded credentials are forbidden.\n- User input must be validated and logs sanitized.\n- Database access belongs behind a service boundary.\n- Tests must be approved before generated production code.\n- Quality failures block acceptance.\n`;
|
|
301
|
+
await fs.mkdir(projectRoot, { recursive: true });
|
|
302
|
+
await fs.writeFile(path.join(projectRoot, ".cursorrules"), content, { mode: 0o644 });
|
|
303
|
+
return content;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async calculateQualityScore(kpis) {
|
|
307
|
+
return calculateQualityScore(kpis);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async #append(name, record) {
|
|
311
|
+
const file = path.join(this.config.projectRoot, QUALITY_ROOT, name);
|
|
312
|
+
const append = async () => {
|
|
313
|
+
let records = [];
|
|
314
|
+
try {
|
|
315
|
+
records = JSON.parse(await fs.readFile(file, "utf8"));
|
|
316
|
+
} catch {
|
|
317
|
+
/* first record */
|
|
318
|
+
}
|
|
319
|
+
if (!Array.isArray(records)) records = [];
|
|
320
|
+
records.push(record);
|
|
321
|
+
await writeJson(file, records.slice(-100));
|
|
322
|
+
};
|
|
323
|
+
appendQueue = appendQueue.then(append, append);
|
|
324
|
+
await appendQueue;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export { calculateQualityScore, staticViolations };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
1
4
|
const ALLOWED_STATUSES = new Set(["pending", "in_progress", "blocked", "failed", "passed"]);
|
|
2
5
|
const REQUIRED_PROOF_FIELDS = [
|
|
3
6
|
"implementationEvidence",
|
|
@@ -5,6 +8,65 @@ const REQUIRED_PROOF_FIELDS = [
|
|
|
5
8
|
"productionEvidence",
|
|
6
9
|
];
|
|
7
10
|
|
|
11
|
+
function compareVersions(left, right) {
|
|
12
|
+
return right
|
|
13
|
+
.split(".")
|
|
14
|
+
.map(Number)
|
|
15
|
+
.reduce((result, value, index) => result || value - Number(left.split(".")[index]), 0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function resolveReleaseIssueGates(root, version = null) {
|
|
19
|
+
return resolveVersionedQaArtifact(root, "release-issue-gates", version);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function resolveReleaseReadiness(root, version = null) {
|
|
23
|
+
return resolveVersionedQaArtifact(root, "release-readiness", version);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function resolveVersionedQaArtifact(root, prefix, version = null) {
|
|
27
|
+
const repoRoot = path.resolve(root);
|
|
28
|
+
let currentVersion = version;
|
|
29
|
+
if (!currentVersion) {
|
|
30
|
+
try {
|
|
31
|
+
currentVersion = JSON.parse(
|
|
32
|
+
fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"),
|
|
33
|
+
).version;
|
|
34
|
+
} catch {
|
|
35
|
+
currentVersion = null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const qaRoot = path.join(repoRoot, "docs", "qa");
|
|
40
|
+
const extension = prefix === "release-readiness" ? "md" : "json";
|
|
41
|
+
const currentPath = currentVersion
|
|
42
|
+
? path.join(qaRoot, `${prefix}-${currentVersion}.${extension}`)
|
|
43
|
+
: null;
|
|
44
|
+
if (currentPath && fs.existsSync(currentPath)) {
|
|
45
|
+
return { path: currentPath, version: currentVersion, fallback: false };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = fs.readdirSync(qaRoot, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const history = entries
|
|
56
|
+
.flatMap((entry) => {
|
|
57
|
+
const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
58
|
+
const match = entry.name.match(
|
|
59
|
+
new RegExp(`^${escapedPrefix}-(\\d+\\.\\d+\\.\\d+)\\.${extension}$`),
|
|
60
|
+
);
|
|
61
|
+
return entry.isFile() && match
|
|
62
|
+
? [{ version: match[1], path: path.join(qaRoot, entry.name) }]
|
|
63
|
+
: [];
|
|
64
|
+
})
|
|
65
|
+
.sort((left, right) => compareVersions(left.version, right.version));
|
|
66
|
+
const previous = history[0];
|
|
67
|
+
return previous ? { ...previous, fallback: true } : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
8
70
|
function isNonEmptyString(value) {
|
|
9
71
|
return typeof value === "string" && value.trim().length > 0;
|
|
10
72
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
|
|
4
|
+
const execFileAsync = promisify(execFile);
|
|
5
|
+
const branchPattern = /^[A-Za-z0-9._/-]+$/;
|
|
6
|
+
|
|
7
|
+
function assertBranch(value, label) {
|
|
8
|
+
if (typeof value !== "string" || !branchPattern.test(value) || value.includes("..")) {
|
|
9
|
+
throw new Error(`${label} is not a safe branch name`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getGitOperation(sourceBranch, targetBranch) {
|
|
14
|
+
assertBranch(sourceBranch, "Source branch");
|
|
15
|
+
assertBranch(targetBranch, "Target branch");
|
|
16
|
+
return {
|
|
17
|
+
command: "git",
|
|
18
|
+
args: ["merge", "--no-ff", "--no-edit", "--", sourceBranch],
|
|
19
|
+
display: `git merge --no-ff --no-edit -- ${sourceBranch}`,
|
|
20
|
+
sourceBranch,
|
|
21
|
+
targetBranch,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function inspectGitOperation(root, sourceBranch, targetBranch) {
|
|
26
|
+
const operation = getGitOperation(sourceBranch, targetBranch);
|
|
27
|
+
try {
|
|
28
|
+
const [branch, status] = await Promise.all([
|
|
29
|
+
execFileAsync("git", ["branch", "--show-current"], { cwd: root, encoding: "utf8" }),
|
|
30
|
+
execFileAsync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8" }),
|
|
31
|
+
execFileAsync("git", ["rev-parse", "--verify", `refs/heads/${sourceBranch}`], {
|
|
32
|
+
cwd: root,
|
|
33
|
+
encoding: "utf8",
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
36
|
+
return {
|
|
37
|
+
...operation,
|
|
38
|
+
available: true,
|
|
39
|
+
currentBranch: branch.stdout.trim(),
|
|
40
|
+
clean: status.stdout.trim() === "",
|
|
41
|
+
sourceExists: true,
|
|
42
|
+
blockers: status.stdout.trim() === "" ? [] : ["working tree is not clean"],
|
|
43
|
+
};
|
|
44
|
+
} catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
...operation,
|
|
47
|
+
available: false,
|
|
48
|
+
currentBranch: "",
|
|
49
|
+
clean: false,
|
|
50
|
+
sourceExists: false,
|
|
51
|
+
blockers: [error?.stderr?.trim() || "Git preflight failed"],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function executeGitOperation(root, operation) {
|
|
57
|
+
const result = await execFileAsync(operation.command, operation.args, {
|
|
58
|
+
cwd: root,
|
|
59
|
+
encoding: "utf8",
|
|
60
|
+
});
|
|
61
|
+
return { ...operation, stdout: result.stdout.trim(), stderr: result.stderr.trim() };
|
|
62
|
+
}
|
package/src/release-state.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { Agents } from "@jstn-sdk/agents";
|
|
3
|
+
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const agentCompatMetadata = require("@jstn-sdk/agents/package.json");
|
|
6
|
+
|
|
7
|
+
export const agentCompatPackage = "@jstn-sdk/agents";
|
|
8
|
+
export const agentCompatVersion = agentCompatMetadata.version;
|
|
9
|
+
|
|
10
|
+
export function listAgentCompatAdapters() {
|
|
11
|
+
return Agents.list().map(({ id, vendor, product, surface, support, verification }) => ({
|
|
12
|
+
id,
|
|
13
|
+
vendor,
|
|
14
|
+
product,
|
|
15
|
+
surface,
|
|
16
|
+
support,
|
|
17
|
+
verification,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function detectAgentEnvironments(root) {
|
|
22
|
+
return Agents.detect(root);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function compileAgentIntegrations(manifest, options = {}) {
|
|
26
|
+
return Agents.compile(manifest, options);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function validateAgentIntegrations(root, options = {}) {
|
|
30
|
+
return Agents.validate(root, options);
|
|
31
|
+
}
|
|
File without changes
|