@cassiomc1/forgeloop 1.8.1 → 1.9.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/.cursor/rules/project-loop.mdc +6 -3
- package/.github/copilot-instructions.md +5 -0
- package/AGENTS.md +6 -0
- package/CLAUDE.md +6 -0
- package/DOCS_INDEX.md +5 -0
- package/ENG/accessibility-eng.md +12 -2
- package/ENG/design-code-eng.md +22 -1
- package/LOOP_ENGINEERING.md +28 -0
- package/PROTOCOL_INTEGRATION.md +26 -0
- package/QUALITY_SCORECARD.md +2 -0
- package/README.md +11 -0
- package/THREAT_MODEL.md +24 -0
- package/completions/_forgeloop +4 -1
- package/completions/forgeloop.bash +7 -1
- package/completions/forgeloop.fish +19 -1
- package/docs/AGENT_PROTOCOL_SUMMARY.md +6 -1
- package/docs/ARTIFACT_REFERENCE.md +128 -0
- package/docs/CLI_REFERENCE.md +84 -1
- package/docs/KNOWLEDGE_SOURCES.md +161 -0
- package/docs/MCP.md +1 -1
- package/docs/RECIPES.md +31 -0
- package/docs/STRUCTURAL_QUALITY.md +350 -0
- package/docs/TROUBLESHOOTING.md +107 -0
- package/package.json +3 -1
- package/schemas/config.schema.json +46 -0
- package/schemas/preflight.schema.json +2 -1
- package/schemas/structural-quality.schema.json +175 -0
- package/src/cli.js +18 -0
- package/src/commands/quality-baseline.js +28 -0
- package/src/commands/quality-status.js +34 -0
- package/src/commands/quality-verify.js +30 -0
- package/src/core/artifact-registry.js +12 -0
- package/src/core/audit.js +38 -0
- package/src/core/bundles.js +134 -1
- package/src/core/cli-command-definitions.js +45 -0
- package/src/core/command-executors.js +16 -0
- package/src/core/command-input.js +12 -0
- package/src/core/completion-artifacts.js +2 -0
- package/src/core/completion.js +42 -0
- package/src/core/config.js +3 -0
- package/src/core/error-codes.js +73 -0
- package/src/core/filesystem.js +18 -3
- package/src/core/inspect.js +64 -0
- package/src/core/integration-invocation-policy.js +15 -0
- package/src/core/integration-resources.js +17 -0
- package/src/core/next-action-model.js +11 -1
- package/src/core/next-action-phases.js +84 -5
- package/src/core/phase.js +9 -1
- package/src/core/preflight.js +33 -0
- package/src/core/protocol-info.js +15 -0
- package/src/core/runtime-context.js +27 -0
- package/src/core/schema-validation.js +1 -0
- package/src/core/structural-quality/artifacts.js +329 -0
- package/src/core/structural-quality/constants.js +67 -0
- package/src/core/structural-quality/policy.js +227 -0
- package/src/core/structural-quality/provider.js +287 -0
- package/src/core/structural-quality/sentrux-mcp.js +477 -0
- package/src/core/structural-quality/service.js +1138 -0
- package/src/core/structural-quality/source-fingerprint.js +112 -0
- package/src/core/structural-quality/status.js +3 -0
- package/src/core/task-paths.js +24 -0
- package/src/core/templates.js +1 -0
- package/src/integration.d.ts +25 -0
- package/src/integration.js +14 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { lstat, readdir } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { canonicalFingerprint } from "../artifacts.js";
|
|
5
|
+
import { ensureWithin, readBytes } from "../filesystem.js";
|
|
6
|
+
import { sha256 } from "../manifest.js";
|
|
7
|
+
import { repositoryHead, repositoryStatusEntries } from "../repository.js";
|
|
8
|
+
import { E_STRUCTURAL_QUALITY_SOURCE_FINGERPRINT_UNAVAILABLE } from "../error-codes.js";
|
|
9
|
+
|
|
10
|
+
// Provider-owned configuration is bound by the provider scope resolver, not
|
|
11
|
+
// treated as project source. This keeps custom providers independent of
|
|
12
|
+
// Sentrux's optional rules file.
|
|
13
|
+
const IGNORED_DIRECTORIES = new Set([".forgeloop", ".git", ".sentrux", "node_modules"]);
|
|
14
|
+
|
|
15
|
+
function fingerprintError(message, cause = null) {
|
|
16
|
+
const error = new Error(message);
|
|
17
|
+
error.code = E_STRUCTURAL_QUALITY_SOURCE_FINGERPRINT_UNAVAILABLE;
|
|
18
|
+
if (cause) error.cause = cause;
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function readMaterialFile(root, relativePath) {
|
|
23
|
+
const fullPath = ensureWithin(root, relativePath);
|
|
24
|
+
let info;
|
|
25
|
+
try {
|
|
26
|
+
info = await lstat(fullPath);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw fingerprintError(`Unable to inspect structural-quality source material: ${relativePath}`, error);
|
|
29
|
+
}
|
|
30
|
+
if (info.isSymbolicLink()) {
|
|
31
|
+
throw fingerprintError(`Structural-quality source material uses an unsafe symlink: ${relativePath}`);
|
|
32
|
+
}
|
|
33
|
+
if (!info.isFile()) {
|
|
34
|
+
throw fingerprintError(`Structural-quality source material is not a regular file: ${relativePath}`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return sha256(await readBytes(fullPath));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw fingerprintError(`Unable to read structural-quality source material: ${relativePath}`, error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function crawlDirectory(root, current = "") {
|
|
44
|
+
const dirPath = current ? path.join(root, current) : root;
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = await readdir(dirPath, { withFileTypes: true });
|
|
48
|
+
} catch (error) {
|
|
49
|
+
throw fingerprintError(`Unable to enumerate structural-quality source material: ${current || "."}`, error);
|
|
50
|
+
}
|
|
51
|
+
const files = [];
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const rel = current ? path.join(current, entry.name).replaceAll("\\", "/") : entry.name;
|
|
54
|
+
if (entry.isSymbolicLink()) {
|
|
55
|
+
throw fingerprintError(`Structural-quality source material uses an unsafe symlink: ${rel}`);
|
|
56
|
+
}
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
if (IGNORED_DIRECTORIES.has(entry.name) || rel.startsWith(".forgeloop/")) continue;
|
|
59
|
+
files.push(...(await crawlDirectory(root, rel)));
|
|
60
|
+
} else if (entry.isFile()) {
|
|
61
|
+
if (rel.startsWith(".forgeloop/")) continue;
|
|
62
|
+
files.push({ path: rel, sha256: await readMaterialFile(root, rel) });
|
|
63
|
+
} else {
|
|
64
|
+
throw fingerprintError(`Unsupported structural-quality source material: ${rel}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return files.sort((left, right) => left.path.localeCompare(right.path));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function computeMaterialSourceFingerprint(target) {
|
|
71
|
+
try {
|
|
72
|
+
const head = await repositoryHead(target);
|
|
73
|
+
const statusEntries = await repositoryStatusEntries(target);
|
|
74
|
+
const filtered = statusEntries
|
|
75
|
+
.filter((entry) => !entry.path.startsWith(".forgeloop/")
|
|
76
|
+
&& !entry.path.startsWith(".git/")
|
|
77
|
+
&& !entry.path.startsWith(".sentrux/"));
|
|
78
|
+
const enriched = [];
|
|
79
|
+
for (const entry of filtered) {
|
|
80
|
+
let digest = "DELETED";
|
|
81
|
+
let exists = false;
|
|
82
|
+
try {
|
|
83
|
+
await lstat(ensureWithin(target, entry.path));
|
|
84
|
+
exists = true;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error.code !== "ENOENT") throw fingerprintError(`Unable to inspect structural-quality source material: ${entry.path}`, error);
|
|
87
|
+
}
|
|
88
|
+
if (exists) {
|
|
89
|
+
digest = await readMaterialFile(target, entry.path);
|
|
90
|
+
}
|
|
91
|
+
enriched.push({
|
|
92
|
+
path: entry.path,
|
|
93
|
+
sourcePath: entry.sourcePath ?? null,
|
|
94
|
+
status: entry.status,
|
|
95
|
+
digest,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
enriched.sort((left, right) => left.path.localeCompare(right.path));
|
|
99
|
+
return canonicalFingerprint({
|
|
100
|
+
kind: "GIT_WORKTREE",
|
|
101
|
+
head,
|
|
102
|
+
statusEntries: enriched,
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error?.code === E_STRUCTURAL_QUALITY_SOURCE_FINGERPRINT_UNAVAILABLE) throw error;
|
|
106
|
+
const files = await crawlDirectory(target);
|
|
107
|
+
return canonicalFingerprint({
|
|
108
|
+
kind: "DIRECTORY",
|
|
109
|
+
files,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
package/src/core/task-paths.js
CHANGED
|
@@ -26,6 +26,7 @@ export const TASK_ARTIFACT_FILES = Object.freeze({
|
|
|
26
26
|
responsibility: "responsibility.json",
|
|
27
27
|
verificationScope: "verification-scope.json",
|
|
28
28
|
attestations: "attestations",
|
|
29
|
+
structuralQuality: "structural-quality",
|
|
29
30
|
});
|
|
30
31
|
|
|
31
32
|
export const POLICY_ROOT = ".forgeloop/policy";
|
|
@@ -156,6 +157,28 @@ export function taskAttestationHistoryDirectory(taskId, verificationCycle) {
|
|
|
156
157
|
return `${taskAttestationDirectory(taskId)}/history/cycle-${verificationCycle}`;
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
export function taskStructuralQualityDirectory(taskId) {
|
|
161
|
+
return `${taskDirectory(taskId)}/${TASK_ARTIFACT_FILES.structuralQuality}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function taskStructuralQualityBaselinePath(taskId) {
|
|
165
|
+
return `${taskStructuralQualityDirectory(taskId)}/baseline.json`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function taskStructuralQualityEvaluationsDirectory(taskId) {
|
|
169
|
+
return `${taskStructuralQualityDirectory(taskId)}/evaluations`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function taskStructuralQualityEvaluationPath(taskId, verificationCycle, attempt) {
|
|
173
|
+
if (!Number.isInteger(verificationCycle) || verificationCycle < 1) {
|
|
174
|
+
throw new Error(`Invalid structural quality verification cycle: ${verificationCycle}`);
|
|
175
|
+
}
|
|
176
|
+
if (!Number.isInteger(attempt) || attempt < 1) {
|
|
177
|
+
throw new Error(`Invalid structural quality evaluation attempt: ${attempt}`);
|
|
178
|
+
}
|
|
179
|
+
return `${taskStructuralQualityEvaluationsDirectory(taskId)}/cycle-${verificationCycle}-attempt-${attempt}.json`;
|
|
180
|
+
}
|
|
181
|
+
|
|
159
182
|
export function taskCodeManifestHistoryPath(taskId, verificationCycle) {
|
|
160
183
|
return `${taskAttestationHistoryDirectory(taskId, verificationCycle)}/code-manifest.json`;
|
|
161
184
|
}
|
|
@@ -203,6 +226,7 @@ export function buildTaskArtifactPaths(taskId) {
|
|
|
203
226
|
responsibility: `${dir}/${TASK_ARTIFACT_FILES.responsibility}`,
|
|
204
227
|
verificationScope: `${dir}/${TASK_ARTIFACT_FILES.verificationScope}`,
|
|
205
228
|
attestations: `${dir}/${TASK_ARTIFACT_FILES.attestations}`,
|
|
229
|
+
structuralQuality: `${dir}/${TASK_ARTIFACT_FILES.structuralQuality}`,
|
|
206
230
|
codeManifest: taskCodeManifestPath(taskId),
|
|
207
231
|
attestationStatement: taskAttestationStatementPath(taskId),
|
|
208
232
|
attestationBundle: taskAttestationBundlePath(taskId),
|
package/src/core/templates.js
CHANGED
|
@@ -87,6 +87,7 @@ export const TEMPLATE_PATHS = [
|
|
|
87
87
|
"schemas/execution-profile-benchmark-scenario.schema.json",
|
|
88
88
|
"schemas/execution-profile-benchmark-run.schema.json",
|
|
89
89
|
"schemas/execution-profile-benchmark-aggregate.schema.json",
|
|
90
|
+
"schemas/structural-quality.schema.json",
|
|
90
91
|
];
|
|
91
92
|
|
|
92
93
|
export function getPackageRoot() {
|
package/src/integration.d.ts
CHANGED
|
@@ -60,8 +60,33 @@ export interface ForgeLoopContext {
|
|
|
60
60
|
verificationExecutionAdapter?: Record<string, unknown>;
|
|
61
61
|
verificationExecutionPolicy?: { requiredIsolation: string };
|
|
62
62
|
usageProvider?: ForgeLoopUsageProvider;
|
|
63
|
+
structuralQualityProviders?: Readonly<Record<string, ForgeLoopStructuralQualityProvider | ForgeLoopStructuralQualityProviderFactory>>;
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
export interface ForgeLoopStructuralQualityProviderInput {
|
|
67
|
+
projectPath: string;
|
|
68
|
+
taskId: string;
|
|
69
|
+
timeoutMs: number;
|
|
70
|
+
maxOutputBytes: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface ForgeLoopStructuralQualityProvider {
|
|
74
|
+
id: string;
|
|
75
|
+
detect(input: ForgeLoopStructuralQualityProviderInput): Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
76
|
+
scan(input: ForgeLoopStructuralQualityProviderInput): Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type ForgeLoopStructuralQualityProviderFactory = (input: ForgeLoopStructuralQualityProviderInput) => ForgeLoopStructuralQualityProvider | Promise<ForgeLoopStructuralQualityProvider>;
|
|
80
|
+
|
|
81
|
+
export declare const STRUCTURAL_QUALITY_ROOT_CAUSES: readonly ["modularity", "acyclicity", "depth", "equality", "redundancy"];
|
|
82
|
+
export declare const STRUCTURAL_QUALITY_MODES: readonly ["off", "observe", "gate"];
|
|
83
|
+
export declare const STRUCTURAL_QUALITY_STATUSES: readonly ["PASS", "FAIL", "BLOCKED", "NOT_OBSERVED"];
|
|
84
|
+
export declare const STRUCTURAL_QUALITY_CHECK_ID: "structural-quality";
|
|
85
|
+
export declare const STRUCTURAL_QUALITY_REQUIREMENT: string;
|
|
86
|
+
export declare function assertStructuralQualityProvider(provider: unknown): ForgeLoopStructuralQualityProvider;
|
|
87
|
+
export declare function normalizeStructuralQualitySnapshot(input: unknown, options?: { projectPath?: string | null }): Record<string, unknown>;
|
|
88
|
+
export declare function normalizeStructuralQualityDetection(input?: Record<string, unknown>, defaults?: Record<string, unknown>): Record<string, unknown>;
|
|
89
|
+
|
|
65
90
|
export interface ForgeLoopUsageProvider {
|
|
66
91
|
getTaskUsage(input: { projectPath: string; taskId: string }): Promise<ForgeLoopUsage | Record<string, unknown>> | ForgeLoopUsage | Record<string, unknown>;
|
|
67
92
|
}
|
package/src/integration.js
CHANGED
|
@@ -45,6 +45,20 @@ export {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
export { createForgeLoopContext } from "./core/runtime-context.js";
|
|
48
|
+
export {
|
|
49
|
+
assertStructuralQualityProvider,
|
|
50
|
+
createStructuralQualityProviderRegistry,
|
|
51
|
+
normalizeStructuralQualityDetection,
|
|
52
|
+
normalizeStructuralQualitySnapshot,
|
|
53
|
+
resolveStructuralQualityProvider,
|
|
54
|
+
} from "./core/structural-quality/provider.js";
|
|
55
|
+
export {
|
|
56
|
+
STRUCTURAL_QUALITY_CHECK_ID,
|
|
57
|
+
STRUCTURAL_QUALITY_MODES,
|
|
58
|
+
STRUCTURAL_QUALITY_REQUIREMENT,
|
|
59
|
+
STRUCTURAL_QUALITY_ROOT_CAUSES,
|
|
60
|
+
STRUCTURAL_QUALITY_STATUSES,
|
|
61
|
+
} from "./core/structural-quality/constants.js";
|
|
48
62
|
export {
|
|
49
63
|
PROFILE_CONTEXT_POLICIES,
|
|
50
64
|
buildExecutionProfileContext,
|