@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.
Files changed (64) hide show
  1. package/.cursor/rules/project-loop.mdc +6 -3
  2. package/.github/copilot-instructions.md +5 -0
  3. package/AGENTS.md +6 -0
  4. package/CLAUDE.md +6 -0
  5. package/DOCS_INDEX.md +5 -0
  6. package/ENG/accessibility-eng.md +12 -2
  7. package/ENG/design-code-eng.md +22 -1
  8. package/LOOP_ENGINEERING.md +28 -0
  9. package/PROTOCOL_INTEGRATION.md +26 -0
  10. package/QUALITY_SCORECARD.md +2 -0
  11. package/README.md +11 -0
  12. package/THREAT_MODEL.md +24 -0
  13. package/completions/_forgeloop +4 -1
  14. package/completions/forgeloop.bash +7 -1
  15. package/completions/forgeloop.fish +19 -1
  16. package/docs/AGENT_PROTOCOL_SUMMARY.md +6 -1
  17. package/docs/ARTIFACT_REFERENCE.md +128 -0
  18. package/docs/CLI_REFERENCE.md +84 -1
  19. package/docs/KNOWLEDGE_SOURCES.md +161 -0
  20. package/docs/MCP.md +1 -1
  21. package/docs/RECIPES.md +31 -0
  22. package/docs/STRUCTURAL_QUALITY.md +350 -0
  23. package/docs/TROUBLESHOOTING.md +107 -0
  24. package/package.json +3 -1
  25. package/schemas/config.schema.json +46 -0
  26. package/schemas/preflight.schema.json +2 -1
  27. package/schemas/structural-quality.schema.json +175 -0
  28. package/src/cli.js +18 -0
  29. package/src/commands/quality-baseline.js +28 -0
  30. package/src/commands/quality-status.js +34 -0
  31. package/src/commands/quality-verify.js +30 -0
  32. package/src/core/artifact-registry.js +12 -0
  33. package/src/core/audit.js +38 -0
  34. package/src/core/bundles.js +134 -1
  35. package/src/core/cli-command-definitions.js +45 -0
  36. package/src/core/command-executors.js +16 -0
  37. package/src/core/command-input.js +12 -0
  38. package/src/core/completion-artifacts.js +2 -0
  39. package/src/core/completion.js +42 -0
  40. package/src/core/config.js +3 -0
  41. package/src/core/error-codes.js +73 -0
  42. package/src/core/filesystem.js +18 -3
  43. package/src/core/inspect.js +64 -0
  44. package/src/core/integration-invocation-policy.js +15 -0
  45. package/src/core/integration-resources.js +17 -0
  46. package/src/core/next-action-model.js +11 -1
  47. package/src/core/next-action-phases.js +84 -5
  48. package/src/core/phase.js +9 -1
  49. package/src/core/preflight.js +33 -0
  50. package/src/core/protocol-info.js +15 -0
  51. package/src/core/runtime-context.js +27 -0
  52. package/src/core/schema-validation.js +1 -0
  53. package/src/core/structural-quality/artifacts.js +329 -0
  54. package/src/core/structural-quality/constants.js +67 -0
  55. package/src/core/structural-quality/policy.js +227 -0
  56. package/src/core/structural-quality/provider.js +287 -0
  57. package/src/core/structural-quality/sentrux-mcp.js +477 -0
  58. package/src/core/structural-quality/service.js +1138 -0
  59. package/src/core/structural-quality/source-fingerprint.js +112 -0
  60. package/src/core/structural-quality/status.js +3 -0
  61. package/src/core/task-paths.js +24 -0
  62. package/src/core/templates.js +1 -0
  63. package/src/integration.d.ts +25 -0
  64. 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
+ }
@@ -0,0 +1,3 @@
1
+ // Read-only structural-quality projections live in the service so lifecycle,
2
+ // integration, and CLI consumers share one canonical source of truth.
3
+ export { projectStructuralQualityStatus } from "./service.js";
@@ -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),
@@ -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() {
@@ -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
  }
@@ -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,