@cassiomc1/forgeloop 1.11.1 → 1.13.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 (71) hide show
  1. package/.github/copilot-instructions.md +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CLAUDE.md +1 -1
  4. package/CONTRIBUTING.md +90 -0
  5. package/DOCS_INDEX.md +13 -11
  6. package/ENG/c-development-eng.md +112 -0
  7. package/ENG/cpp-development-eng.md +109 -0
  8. package/ENG/dotnet-aspnetcore-development-eng.md +401 -0
  9. package/ENG/flutter-development-eng.md +2106 -0
  10. package/ENG/go-development-eng.md +103 -0
  11. package/ENG/java-development-eng.md +125 -0
  12. package/ENG/nodejs-backend-development-eng.md +605 -0
  13. package/ENG/php-development-eng.md +104 -0
  14. package/ENG/rust-development-eng.md +422 -0
  15. package/ENG/sql-development-eng.md +108 -0
  16. package/ENG/swift-development-eng.md +111 -0
  17. package/ENG/typescript-development-eng.md +108 -0
  18. package/GUIDE_ROUTER.md +455 -4
  19. package/LOOP_SYSTEM_DESIGN.md +10 -6
  20. package/QUALITY_SCORECARD.md +2 -0
  21. package/README.md +50 -38
  22. package/THIRD_PARTY_NOTICES.md +19 -7
  23. package/completions/_forgeloop +3 -3
  24. package/completions/forgeloop.bash +3 -3
  25. package/completions/forgeloop.fish +7 -0
  26. package/docs/AGENT_PROTOCOL_SUMMARY.md +55 -2
  27. package/docs/CLI_REFERENCE.md +28 -6
  28. package/docs/DOCUMENTATION_GUIDE.md +2 -1
  29. package/docs/GETTING_STARTED.md +59 -0
  30. package/docs/MCP.md +1 -1
  31. package/docs/PACKAGE_CONTENTS.md +29 -8
  32. package/docs/RECIPES.md +23 -0
  33. package/docs/RELEASE_CHECKLIST.md +51 -9
  34. package/docs/TROUBLESHOOTING.md +100 -2
  35. package/docs/assets/diagrams/forgeloop-engineering-flow.html +19 -6
  36. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  37. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5 -5
  38. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +12 -2
  39. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +3 -3
  40. package/docs/documentation-manifest.json +652 -0
  41. package/docs/protocol-requirements.json +77 -0
  42. package/package.json +19 -4
  43. package/schemas/routing-input.schema.json +14 -1
  44. package/scripts/CI_VALIDATORS.md +84 -11
  45. package/scripts/generate-agent-protocol-summary.mjs +36 -0
  46. package/src/commands/next.js +19 -7
  47. package/src/commands/route.js +5 -0
  48. package/src/commands/task-create.js +84 -25
  49. package/src/commands/task-list.js +22 -2
  50. package/src/config/guides.json +48 -0
  51. package/src/core/build-script.js +151 -0
  52. package/src/core/c-cpp-project.js +143 -0
  53. package/src/core/cli-command-definitions.js +8 -1
  54. package/src/core/command-executors.js +5 -3
  55. package/src/core/command-input.js +140 -102
  56. package/src/core/contract-presets.js +82 -0
  57. package/src/core/error-codes.js +3 -3
  58. package/src/core/filesystem.js +1 -10
  59. package/src/core/go-project.js +206 -0
  60. package/src/core/java-project.js +403 -0
  61. package/src/core/multi-language-project.js +117 -0
  62. package/src/core/next-explanation.js +63 -0
  63. package/src/core/php-project.js +85 -0
  64. package/src/core/project-detection.js +2227 -0
  65. package/src/core/reconcile-closure.js +4 -1
  66. package/src/core/router.js +233 -7
  67. package/src/core/rust-project.js +400 -0
  68. package/src/core/sql-project.js +141 -0
  69. package/src/core/swift-project.js +200 -0
  70. package/src/core/typescript-project.js +349 -0
  71. package/src/core/xml-structure.js +123 -0
@@ -7,6 +7,7 @@ import { createReceipt } from "./receipt.js";
7
7
  import { currentRepositoryFingerprint } from "./repository.js";
8
8
  import { taskArtifactPath } from "./task-paths.js";
9
9
  import { classifyLoadedWorkState, readWorkState, mutateWorkState } from "./work-state.js";
10
+ import { classifyRequirement } from "./evidence-readiness.js";
10
11
 
11
12
  export const RECONCILE_EVENT = "CHECKPOINT_RECONCILED";
12
13
 
@@ -133,7 +134,9 @@ export async function runReconcileClosure({
133
134
  if (typeof item === "string") {
134
135
  return item === requirement;
135
136
  }
136
- return item.type === "VERIFICATION" && item.id === checkId && item.text === requirement;
137
+ return classifyRequirement(item).type === "VERIFICATION"
138
+ && item.id === checkId
139
+ && item.text === requirement;
137
140
  });
138
141
  if (!verificationItem) {
139
142
  throw reconcileError(
@@ -1,5 +1,6 @@
1
1
  import { GUIDE_IDS, PROTOCOL_VERSION } from "./protocol.js";
2
2
  import { assertExecutionProfile, resolveExecutionProfile } from "./execution-profile.js";
3
+ import { PROJECT_EVIDENCE_SCOPES } from "./project-detection.js";
3
4
 
4
5
  export const ROUTING_SCHEMA_VERSION = 1;
5
6
 
@@ -61,6 +62,22 @@ const SIGNALS = Object.freeze({
61
62
  platforms: new Set(["web", "mobile", "desktop", "server", "ci", "cross-platform"]),
62
63
  });
63
64
 
65
+ const PROJECT_FRAMEWORKS = new Set([
66
+ "flutter", "dotnet", "aspnetcore", "abp", "nodejs", "rust",
67
+ "c", "cpp", "java", "sql", "go", "typescript", "php", "swift",
68
+ ]);
69
+
70
+ const LANGUAGE_PROJECT_GUIDES = Object.freeze([
71
+ ["c", "PROJECT_C_CONFIRMED"],
72
+ ["cpp", "PROJECT_CPP_CONFIRMED"],
73
+ ["java", "PROJECT_JAVA_CONFIRMED"],
74
+ ["go", "PROJECT_GO_CONFIRMED"],
75
+ ["typescript", "PROJECT_TYPESCRIPT_CONFIRMED"],
76
+ ["php", "PROJECT_PHP_CONFIRMED"],
77
+ ["swift", "PROJECT_SWIFT_CONFIRMED"],
78
+ ["sql", "PROJECT_SQL_CONFIRMED"],
79
+ ]);
80
+
64
81
  export const PLATFORM_SEMANTICS = Object.freeze({
65
82
  web: Object.freeze({
66
83
  mode: "informational-only",
@@ -121,6 +138,165 @@ const PRIMARY_GUIDES = Object.freeze({
121
138
  "ui-copy": "design",
122
139
  });
123
140
 
141
+ function hasFlutterWorkContext(workType) {
142
+ return !["documentation", "ui-copy"].includes(workType);
143
+ }
144
+
145
+ function addFlutterProjectGuides(input, add) {
146
+ const projectEvidence = input.projectEvidence;
147
+ if (!projectEvidence?.frameworks.includes("flutter")
148
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
149
+ || !hasFlutterWorkContext(input.workType)) return;
150
+ add("flutter", "PROJECT_FLUTTER_SDK_DEPENDENCY");
151
+ }
152
+
153
+ function hasDotNetWorkContext(workType) {
154
+ return !["documentation", "ui-copy"].includes(workType);
155
+ }
156
+
157
+ function addDotNetProjectGuides(input, add) {
158
+ const projectEvidence = input.projectEvidence;
159
+ if (!projectEvidence?.frameworks.includes("dotnet")
160
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
161
+ || !hasDotNetWorkContext(input.workType)) return;
162
+ add("dotnet", "PROJECT_DOTNET_SDK_PROJECT");
163
+ if (projectEvidence.frameworks.includes("aspnetcore")) add("dotnet", "PROJECT_ASPNETCORE_CONFIRMED");
164
+ if (projectEvidence.frameworks.includes("abp")) add("dotnet", "PROJECT_ABP_CONFIRMED");
165
+ }
166
+
167
+ function hasNodeJsWorkContext(workType) {
168
+ return !["documentation", "ui-copy", "mobile-ui"].includes(workType);
169
+ }
170
+
171
+ function addNodeJsProjectGuides(input, add) {
172
+ const projectEvidence = input.projectEvidence;
173
+ if (!projectEvidence?.frameworks.includes("nodejs")
174
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
175
+ || !hasNodeJsWorkContext(input.workType)) return;
176
+ add("nodejs", "PROJECT_NODEJS_CONFIRMED");
177
+ if (projectEvidence.primarySignals.some((signal) => signal.includes(":dependencies.") || signal.includes(":optionalDependencies."))) {
178
+ add("nodejs", "PROJECT_NODEJS_BACKEND_FRAMEWORK");
179
+ }
180
+ if (projectEvidence.primarySignals.some((signal) => signal.includes(":scripts."))) {
181
+ add("nodejs", "PROJECT_NODEJS_RUNTIME_SCRIPT");
182
+ }
183
+ if (projectEvidence.primarySignals.some((signal) => signal.includes(":import="))) {
184
+ add("nodejs", "PROJECT_NODEJS_SERVER_RUNTIME");
185
+ }
186
+ }
187
+
188
+ function hasRustWorkContext(workType) {
189
+ return !["documentation", "ui-copy", "mobile-ui"].includes(workType);
190
+ }
191
+
192
+ function addRustProjectGuides(input, add) {
193
+ const projectEvidence = input.projectEvidence;
194
+ if (!projectEvidence?.frameworks.includes("rust")
195
+ || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
196
+ || !hasRustWorkContext(input.workType)) return;
197
+ add("rust", "PROJECT_RUST_CONFIRMED");
198
+ if (projectEvidence.primarySignals.some((signal) => signal.endsWith(":package"))) {
199
+ add("rust", "PROJECT_RUST_CARGO_PACKAGE");
200
+ }
201
+ if (projectEvidence.primarySignals.some((signal) => signal.endsWith(":workspace"))) {
202
+ add("rust", "PROJECT_RUST_CARGO_WORKSPACE");
203
+ }
204
+ }
205
+
206
+ function hasLanguageWorkContext(workType) {
207
+ return !["documentation", "ui-copy"].includes(workType);
208
+ }
209
+
210
+ function addLanguageProjectGuides(input, add) {
211
+ const projectEvidence = input.projectEvidence;
212
+ if (!projectEvidence || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)
213
+ || !hasLanguageWorkContext(input.workType)) return;
214
+ for (const [guide, reason] of LANGUAGE_PROJECT_GUIDES) {
215
+ if (projectEvidence.frameworks.includes(guide)) add(guide, reason);
216
+ }
217
+ }
218
+
219
+ function addProjectBaselineGuides(input, add) {
220
+ const projectEvidence = input.projectEvidence;
221
+ if (!projectEvidence || !["MATCH", "UNSCOPED"].includes(projectEvidence.scope)) return;
222
+ if (projectEvidence.frameworks.includes("flutter") && hasFlutterWorkContext(input.workType)) {
223
+ add("clean", "PROJECT_FLUTTER_BASELINE");
224
+ add("test", "PROJECT_FLUTTER_BASELINE");
225
+ }
226
+ if (projectEvidence.frameworks.includes("dotnet") && hasDotNetWorkContext(input.workType)) {
227
+ add("clean", "PROJECT_DOTNET_BASELINE");
228
+ add("test", "PROJECT_DOTNET_BASELINE");
229
+ }
230
+ if (projectEvidence.frameworks.includes("nodejs") && hasNodeJsWorkContext(input.workType)) {
231
+ add("clean", "PROJECT_NODEJS_BASELINE");
232
+ add("test", "PROJECT_NODEJS_BASELINE");
233
+ }
234
+ if (projectEvidence.frameworks.includes("rust") && hasRustWorkContext(input.workType)) {
235
+ add("clean", "PROJECT_RUST_BASELINE");
236
+ add("test", "PROJECT_RUST_BASELINE");
237
+ }
238
+ if (hasLanguageWorkContext(input.workType)) {
239
+ for (const [language] of LANGUAGE_PROJECT_GUIDES) {
240
+ if (!projectEvidence.frameworks.includes(language)) continue;
241
+ add("clean", `PROJECT_${language.toUpperCase()}_BASELINE`);
242
+ add("test", `PROJECT_${language.toUpperCase()}_BASELINE`);
243
+ }
244
+ }
245
+ }
246
+
247
+ function flutterExclusionReason(projectEvidence, workType) {
248
+ if (!projectEvidence) return "NO_FLUTTER_PROJECT_EVIDENCE";
249
+ if (projectEvidence.scope === "NO_MATCH") return "NO_FLUTTER_SCOPE_MATCH";
250
+ if (!projectEvidence.frameworks.includes("flutter")) return "NO_FLUTTER_PRIMARY_EVIDENCE";
251
+ if (!hasFlutterWorkContext(workType)) return "NO_FLUTTER_EXECUTABLE_WORK";
252
+ return "NO_FLUTTER_PRIMARY_EVIDENCE";
253
+ }
254
+
255
+ function dotNetExclusionReason(projectEvidence, workType) {
256
+ if (!projectEvidence) return "NO_DOTNET_PROJECT_EVIDENCE";
257
+ if (projectEvidence.scope === "NO_MATCH") return "NO_DOTNET_SCOPE_MATCH";
258
+ if (!projectEvidence.frameworks.includes("dotnet")) return "NO_DOTNET_PRIMARY_EVIDENCE";
259
+ if (!hasDotNetWorkContext(workType)) return "NO_DOTNET_EXECUTABLE_WORK";
260
+ return "NO_DOTNET_PRIMARY_EVIDENCE";
261
+ }
262
+
263
+ function nodeJsExclusionReason(projectEvidence, workType) {
264
+ if (!projectEvidence) return "NO_NODEJS_PROJECT_EVIDENCE";
265
+ if (projectEvidence.scope === "NO_MATCH") return "NO_NODEJS_SCOPE_MATCH";
266
+ if (!projectEvidence.frameworks.includes("nodejs")) return "NO_NODEJS_PRIMARY_EVIDENCE";
267
+ if (!hasNodeJsWorkContext(workType)) return "NO_NODEJS_EXECUTABLE_WORK";
268
+ return "NO_NODEJS_PRIMARY_EVIDENCE";
269
+ }
270
+
271
+ function rustExclusionReason(projectEvidence, workType) {
272
+ if (!projectEvidence) return "NO_RUST_PROJECT_EVIDENCE";
273
+ if (projectEvidence.scope === "NO_MATCH") return "NO_RUST_SCOPE_MATCH";
274
+ if (!projectEvidence.frameworks.includes("rust")) return "NO_RUST_PRIMARY_EVIDENCE";
275
+ if (!hasRustWorkContext(workType)) return "NO_RUST_EXECUTABLE_WORK";
276
+ return "NO_RUST_PRIMARY_EVIDENCE";
277
+ }
278
+
279
+ function exclusionReasonForGuide(guide, projectEvidence, workType) {
280
+ if (guide === "security") return "NO_TRUST_BOUNDARY";
281
+ if (guide === "performance") return "NO_MEASURABLE_PERFORMANCE_RISK";
282
+ if (guide === "design" || guide === "accessibility") return "NO_UI_SURFACE";
283
+ if (guide === "premium" || guide === "games") return "NO_PRIMARY_WORK_TYPE";
284
+ if (guide === "taste") return "NO_TASTE_FRONTEND_CONTEXT";
285
+ if (guide === "documentation") return "NO_DOCUMENTATION_SURFACE";
286
+ if (guide === "flutter") return flutterExclusionReason(projectEvidence, workType);
287
+ if (guide === "dotnet") return dotNetExclusionReason(projectEvidence, workType);
288
+ if (guide === "nodejs") return nodeJsExclusionReason(projectEvidence, workType);
289
+ if (guide === "rust") return rustExclusionReason(projectEvidence, workType);
290
+ if (LANGUAGE_PROJECT_GUIDES.some(([language]) => language === guide)) {
291
+ if (!projectEvidence) return `NO_${guide.toUpperCase()}_PROJECT_EVIDENCE`;
292
+ if (projectEvidence.scope === "NO_MATCH") return `NO_${guide.toUpperCase()}_SCOPE_MATCH`;
293
+ if (!projectEvidence.frameworks.includes(guide)) return `NO_${guide.toUpperCase()}_PRIMARY_EVIDENCE`;
294
+ if (!hasLanguageWorkContext(workType)) return `NO_${guide.toUpperCase()}_EXECUTABLE_WORK`;
295
+ return `NO_${guide.toUpperCase()}_PRIMARY_EVIDENCE`;
296
+ }
297
+ return "NO_BEHAVIOR_OR_EXECUTABLE_CHANGE";
298
+ }
299
+
124
300
  export class RouteInputError extends Error {
125
301
  constructor(message) {
126
302
  super(message);
@@ -151,6 +327,50 @@ function normalizeArray(value, name, allowed) {
151
327
  return [...seen].sort();
152
328
  }
153
329
 
330
+ function normalizeStringList(value, name) {
331
+ if (value === undefined) return [];
332
+ if (!Array.isArray(value)) throw new RouteInputError(`${name} must be an array`);
333
+ const seen = new Set();
334
+ for (const item of value) {
335
+ if (typeof item !== "string" || item.trim() === "") {
336
+ throw new RouteInputError(`${name} must contain non-empty strings`);
337
+ }
338
+ if (seen.has(item)) throw new RouteInputError(`Duplicate ${name.slice(0, -1)}: ${item}`);
339
+ seen.add(item);
340
+ }
341
+ return [...seen].sort();
342
+ }
343
+
344
+ function normalizeProjectEvidence(value) {
345
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
346
+ throw new RouteInputError("projectEvidence must be an object");
347
+ }
348
+ if (value.schemaVersion !== undefined && value.schemaVersion !== ROUTING_SCHEMA_VERSION) {
349
+ throw new RouteInputError(`Unsupported projectEvidence schema version: ${value.schemaVersion}`);
350
+ }
351
+ const frameworks = normalizeArray(value.frameworks, "frameworks", PROJECT_FRAMEWORKS);
352
+ const hasDotnet = frameworks.includes("dotnet");
353
+ const invalidOverlays = frameworks.filter((framework) => framework === "aspnetcore" || framework === "abp");
354
+ if (!hasDotnet && invalidOverlays.length > 0) {
355
+ const noun = invalidOverlays.length === 1 ? "framework" : "frameworks";
356
+ const verb = invalidOverlays.length === 1 ? "requires" : "require";
357
+ const labels = invalidOverlays.map((framework) => `"${framework}"`).join(", ");
358
+ throw new RouteInputError(`projectEvidence ${noun} ${labels} ${verb} "dotnet"`);
359
+ }
360
+ const scope = value.scope ?? (frameworks.length > 0 ? "UNSCOPED" : "NONE");
361
+ if (!PROJECT_EVIDENCE_SCOPES.includes(scope)) {
362
+ throw new RouteInputError(`Unknown project evidence scope: ${scope}`);
363
+ }
364
+ return {
365
+ schemaVersion: ROUTING_SCHEMA_VERSION,
366
+ scope,
367
+ frameworks,
368
+ projectRoots: normalizeStringList(value.projectRoots, "projectRoots"),
369
+ primarySignals: normalizeStringList(value.primarySignals, "primarySignals"),
370
+ supportingSignals: normalizeStringList(value.supportingSignals, "supportingSignals"),
371
+ };
372
+ }
373
+
154
374
  export function normalizeRouteInput(input = {}) {
155
375
  if (!input || typeof input !== "object" || Array.isArray(input)) {
156
376
  throw new RouteInputError("Route input must be an object");
@@ -163,6 +383,9 @@ export function normalizeRouteInput(input = {}) {
163
383
  throw new RouteInputError(`${key} must be boolean`);
164
384
  }
165
385
  }
386
+ const projectEvidence = input.projectEvidence === undefined
387
+ ? undefined
388
+ : normalizeProjectEvidence(input.projectEvidence);
166
389
  return {
167
390
  schemaVersion: ROUTING_SCHEMA_VERSION,
168
391
  workType: input.workType,
@@ -171,6 +394,7 @@ export function normalizeRouteInput(input = {}) {
171
394
  platforms: normalizeArray(input.platforms, "platforms", SIGNALS.platforms),
172
395
  behaviorChange: input.behaviorChange ?? false,
173
396
  executableChange: input.executableChange ?? false,
397
+ ...(projectEvidence ? { projectEvidence } : {}),
174
398
  };
175
399
  }
176
400
 
@@ -186,6 +410,14 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
186
410
  selected.set(guide, reasons);
187
411
  }
188
412
 
413
+ const projectEvidence = normalized.projectEvidence;
414
+ addFlutterProjectGuides(normalized, add);
415
+ addDotNetProjectGuides(normalized, add);
416
+ addNodeJsProjectGuides(normalized, add);
417
+ addRustProjectGuides(normalized, add);
418
+ addLanguageProjectGuides(normalized, add);
419
+ addProjectBaselineGuides(normalized, add);
420
+
189
421
  const workReason = reasonForWorkType(normalized.workType);
190
422
  for (const guide of WORK_GUIDES[normalized.workType]) add(guide, workReason);
191
423
 
@@ -246,13 +478,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
246
478
 
247
479
  for (const guide of GUIDE_IDS) {
248
480
  if (selected.has(guide)) continue;
249
- if (guide === "security") excluded[guide] = ["NO_TRUST_BOUNDARY"];
250
- else if (guide === "performance") excluded[guide] = ["NO_MEASURABLE_PERFORMANCE_RISK"];
251
- else if (guide === "design" || guide === "accessibility") excluded[guide] = ["NO_UI_SURFACE"];
252
- else if (guide === "premium" || guide === "games") excluded[guide] = ["NO_PRIMARY_WORK_TYPE"];
253
- else if (guide === "taste") excluded[guide] = ["NO_TASTE_FRONTEND_CONTEXT"];
254
- else if (guide === "documentation") excluded[guide] = ["NO_DOCUMENTATION_SURFACE"];
255
- else excluded[guide] = ["NO_BEHAVIOR_OR_EXECUTABLE_CHANGE"];
481
+ excluded[guide] = [exclusionReasonForGuide(guide, projectEvidence, normalized.workType)];
256
482
  }
257
483
 
258
484
  const guides = [...selected.keys()];