@cassiomc1/forgeloop 1.1.1 → 1.2.2

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 (79) hide show
  1. package/.cursor/rules/project-loop.mdc +1 -1
  2. package/.github/copilot-instructions.md +1 -1
  3. package/AGENTS.md +1 -1
  4. package/CLAUDE.md +1 -1
  5. package/DOCS_INDEX.md +3 -0
  6. package/ENG/design-code-eng.md +124 -0
  7. package/ENG/premium-sites-studio-eng.md +28 -0
  8. package/ENG/taste-frontend-eng.md +3 -2
  9. package/ENG/test-code-eng.md +45 -0
  10. package/LOOP_ENGINEERING.md +74 -0
  11. package/LOOP_SYSTEM_DESIGN.md +9 -5
  12. package/ORCHESTRATOR_INTEGRATION.md +41 -6
  13. package/PROTOCOL_INTEGRATION.md +13 -0
  14. package/README.md +40 -6
  15. package/TERMINOLOGY.md +10 -0
  16. package/THIRD_PARTY_NOTICES.md +58 -1
  17. package/THREAT_MODEL.md +12 -1
  18. package/docs/ARTIFACT_REFERENCE.md +152 -2
  19. package/docs/CLI_REFERENCE.md +346 -30
  20. package/docs/CROSS_HARNESS_CONTINUITY.md +1 -0
  21. package/docs/DOCUMENTATION_GUIDE.md +41 -4
  22. package/docs/GETTING_STARTED.md +39 -8
  23. package/docs/RECIPES.md +66 -7
  24. package/docs/TROUBLESHOOTING.md +279 -6
  25. package/package.json +1 -1
  26. package/schemas/policy-baseline.schema.json +26 -0
  27. package/schemas/policy-discovery.schema.json +45 -0
  28. package/schemas/policy-lock.schema.json +16 -0
  29. package/schemas/policy-rules.schema.json +48 -0
  30. package/schemas/policy-snapshot.schema.json +16 -0
  31. package/src/cli.js +102 -1
  32. package/src/commands/baseline.js +120 -0
  33. package/src/commands/init.js +304 -6
  34. package/src/commands/next.js +15 -1
  35. package/src/commands/policy-diff.js +51 -0
  36. package/src/commands/policy-discover.js +42 -0
  37. package/src/commands/policy-status.js +33 -0
  38. package/src/commands/profile-interview.js +50 -0
  39. package/src/commands/progress.js +51 -0
  40. package/src/commands/reconcile-closure.js +49 -0
  41. package/src/commands/record-decision-criterion.js +34 -0
  42. package/src/commands/record-diagnosis.js +49 -0
  43. package/src/commands/rule-verify.js +36 -0
  44. package/src/commands/validate-receipt.js +38 -3
  45. package/src/core/artifact-registry.js +60 -0
  46. package/src/core/audit.js +24 -0
  47. package/src/core/cli-command-definitions.js +163 -7
  48. package/src/core/cli-metadata.js +1 -1
  49. package/src/core/completion-artifacts.js +29 -3
  50. package/src/core/completion.js +101 -10
  51. package/src/core/diagnosis-model.js +214 -0
  52. package/src/core/diagnosis.js +171 -0
  53. package/src/core/error-codes.js +292 -0
  54. package/src/core/events.js +47 -1
  55. package/src/core/execution-prerequisites.js +38 -20
  56. package/src/core/execution.js +20 -3
  57. package/src/core/native-adapters.js +14 -4
  58. package/src/core/next-action-model.js +40 -5
  59. package/src/core/next-action.js +234 -91
  60. package/src/core/phase.js +29 -0
  61. package/src/core/policy-adapters.js +276 -0
  62. package/src/core/policy-baseline.js +144 -0
  63. package/src/core/policy-diff.js +133 -0
  64. package/src/core/policy-discovery.js +225 -0
  65. package/src/core/policy-engine.js +533 -0
  66. package/src/core/policy-mutation.js +139 -0
  67. package/src/core/preflight-consistency.js +23 -15
  68. package/src/core/preflight-model.js +10 -2
  69. package/src/core/preflight.js +65 -1
  70. package/src/core/progress.js +143 -0
  71. package/src/core/protocol.js +8 -0
  72. package/src/core/reconcile-closure.js +173 -0
  73. package/src/core/schema-validation.js +6 -0
  74. package/src/core/settlement-model.js +85 -0
  75. package/src/core/settlement.js +78 -0
  76. package/src/core/task-context.js +11 -0
  77. package/src/core/task-discovery.js +67 -1
  78. package/src/core/task-paths.js +9 -0
  79. package/src/core/templates.js +5 -0
@@ -0,0 +1,78 @@
1
+ import { readContract } from "./contract.js";
2
+ import { appendProtocolEvent, validateEventLedger } from "./events.js";
3
+ import {
4
+ assertDecisionCriterionDetails,
5
+ criterionForDecision,
6
+ decisionCriterionEvents,
7
+ decisionId,
8
+ normalizeDecisionCriterionInput,
9
+ normalizeDecisionText,
10
+ } from "./settlement-model.js";
11
+
12
+ export {
13
+ assertDecisionCriterionDetails,
14
+ criterionForDecision,
15
+ decisionCriterionEvents,
16
+ decisionId,
17
+ normalizeDecisionCriterionInput,
18
+ normalizeDecisionText,
19
+ };
20
+
21
+ export async function recordDecisionCriterion({
22
+ target,
23
+ packageRoot,
24
+ decision,
25
+ settledBy,
26
+ taskId = null,
27
+ contractPath = null,
28
+ eventsPath = null,
29
+ }) {
30
+ const normalized = normalizeDecisionCriterionInput({ decision, settledBy });
31
+ const contract = await readContract(target, packageRoot, { taskId, contractPath });
32
+ if (!contract || !contract.value) {
33
+ const error = new Error("Current contract not found");
34
+ error.code = "E_CONTRACT_MISSING";
35
+ throw error;
36
+ }
37
+
38
+ const unresolved = contract.value.unresolvedDecisions ?? [];
39
+ if (!unresolved.includes(normalized.decision)) {
40
+ const error = new Error(`Decision "${normalized.decision}" is not present in current unresolvedDecisions`);
41
+ error.code = "E_DECISION_NOT_UNRESOLVED";
42
+ throw error;
43
+ }
44
+
45
+ const ledger = await validateEventLedger(target, packageRoot, { taskId: taskId ?? null, eventsPath });
46
+ if (!ledger.valid) {
47
+ const first = ledger.errors[0];
48
+ const error = new Error(first.message);
49
+ error.code = first.code;
50
+ throw error;
51
+ }
52
+
53
+ const decId = decisionId(normalized.decision);
54
+ const details = {
55
+ decision: normalized.decision,
56
+ decisionId: decId,
57
+ settledBy: normalized.settledBy,
58
+ contractFingerprint: contract.fingerprint,
59
+ };
60
+
61
+ assertDecisionCriterionDetails(details);
62
+
63
+ const event = await appendProtocolEvent(
64
+ target,
65
+ {
66
+ taskId: contract.value.taskId,
67
+ event: "DECISION_CRITERION_RECORDED",
68
+ details,
69
+ },
70
+ packageRoot,
71
+ { taskId: taskId ?? null, eventsPath },
72
+ );
73
+
74
+ return {
75
+ event,
76
+ criterion: details,
77
+ };
78
+ }
@@ -71,6 +71,17 @@ export async function resolveTaskContext(target, {
71
71
  const tasks = await discoverTasks(target, packageRoot);
72
72
  const healthyTasks = tasks.filter((t) => t.healthy !== false);
73
73
 
74
+ // Fail closed: modern task namespaces exist but are all corrupt/unhealthy.
75
+ // Never fall back to legacy singleton state over corrupt modern state.
76
+ if (tasks.length > 0 && healthyTasks.length === 0) {
77
+ const firstInvalid = tasks.find((t) => t.healthy === false);
78
+ const error = new Error(
79
+ firstInvalid?.error?.message ?? "Task namespaces exist but none are valid",
80
+ );
81
+ error.code = firstInvalid?.error?.code ?? "E_TASK_DESCRIPTOR_INVALID";
82
+ throw error;
83
+ }
84
+
74
85
  if (healthyTasks.length === 1) {
75
86
  const single = healthyTasks[0];
76
87
  return createTaskContext({
@@ -1,12 +1,59 @@
1
1
  import { readdir } from "node:fs/promises";
2
2
  import { ensureWithin, fileExists } from "./filesystem.js";
3
3
  import { getPackageRoot } from "./templates.js";
4
- import { TASK_STATE_ROOT, taskArtifactPath } from "./task-paths.js";
4
+ import { TASK_STATE_ROOT, TASK_ARTIFACT_FILES, taskArtifactPath } from "./task-paths.js";
5
5
  import { readTaskDescriptor } from "./task-descriptor.js";
6
6
  import { readJsonArtifact } from "./artifacts.js";
7
7
  import { readLockInfo } from "./task-lock.js";
8
8
  import { taskStorageKey } from "./task-identity.js";
9
9
 
10
+ /**
11
+ * Explicitly recognized legacy-incidental artifacts that may legitimately
12
+ * exist inside a 64-hex task-state directory WITHOUT a task.json descriptor.
13
+ * A legacy preflight writes a task-scoped policy snapshot for a task that has
14
+ * no modern namespace; that directory is not a task namespace. Any other
15
+ * content makes the directory a corrupt modern task namespace that must fail
16
+ * closed.
17
+ */
18
+ const LEGACY_INCIDENTAL_ARTIFACTS = new Set([
19
+ TASK_ARTIFACT_FILES.policySnapshot,
20
+ ]);
21
+
22
+ /**
23
+ * Classifies a 64-hex task-state directory whose task.json is missing.
24
+ *
25
+ * - directory contains only explicitly recognized legacy-incidental
26
+ * artifacts (policy-snapshot.json) -> LEGACY_INCIDENTAL (ignored)
27
+ * - anything else, including an empty directory -> CORRUPT_TASK_NAMESPACE
28
+ * (no positive evidence of legitimate legacy spillover)
29
+ */
30
+ async function classifyDescriptorlessTaskDirectory(target, taskKey) {
31
+ const directory = ensureWithin(target, `${TASK_STATE_ROOT}/${taskKey}`);
32
+ let entries = [];
33
+ try {
34
+ entries = await readdir(directory, { withFileTypes: true });
35
+ } catch {
36
+ return {
37
+ kind: "CORRUPT_TASK_NAMESPACE",
38
+ error: {
39
+ code: "E_TASK_DESCRIPTOR_INVALID",
40
+ message: `Task namespace ${taskKey} is missing task.json`,
41
+ },
42
+ };
43
+ }
44
+ const names = new Set(entries.map((entry) => entry.name));
45
+ if (names.size > 0 && [...names].every((name) => LEGACY_INCIDENTAL_ARTIFACTS.has(name))) {
46
+ return { kind: "LEGACY_INCIDENTAL" };
47
+ }
48
+ return {
49
+ kind: "CORRUPT_TASK_NAMESPACE",
50
+ error: {
51
+ code: "E_TASK_DESCRIPTOR_INVALID",
52
+ message: `Task namespace ${taskKey} contains task artifacts but task.json is missing`,
53
+ },
54
+ };
55
+ }
56
+
10
57
  export async function discoverTasks(target, packageRoot = getPackageRoot()) {
11
58
  const rootPath = ensureWithin(target, TASK_STATE_ROOT);
12
59
  if (!(await fileExists(rootPath))) {
@@ -92,6 +139,25 @@ export async function discoverTasks(target, packageRoot = getPackageRoot()) {
92
139
  directory: `${TASK_STATE_ROOT}/${descriptor.taskKey}`,
93
140
  });
94
141
  } catch (err) {
142
+ // A directory without a task.json descriptor is not automatically a
143
+ // task namespace: classify by contents so explicitly recognized legacy
144
+ // spillover (policy-snapshot.json) stays compatible while any modern
145
+ // task artifact without a descriptor fails closed instead of silently
146
+ // reopening the legacy singleton fallback.
147
+ if (err.code === "E_TASK_NOT_FOUND" || err.code === "ARTIFACT_MISSING") {
148
+ const classification = await classifyDescriptorlessTaskDirectory(target, entry.name);
149
+ if (classification.kind === "LEGACY_INCIDENTAL") {
150
+ continue;
151
+ }
152
+ tasks.push({
153
+ taskId: null,
154
+ taskKey: entry.name,
155
+ directory: `${TASK_STATE_ROOT}/${entry.name}`,
156
+ healthy: false,
157
+ error: classification.error,
158
+ });
159
+ continue;
160
+ }
95
161
  // P1-2: Surface corrupt task namespaces instead of silently hiding them
96
162
  tasks.push({
97
163
  taskId: null,
@@ -15,14 +15,22 @@ export const TASK_ARTIFACT_FILES = Object.freeze({
15
15
  gates: "gates",
16
16
  executions: "executions",
17
17
  lock: ".lock",
18
+ policySnapshot: "policy-snapshot.json",
18
19
  });
19
20
 
21
+ export const POLICY_ROOT = ".forgeloop/policy";
22
+
20
23
  export const PROJECT_ARTIFACT_PATHS = Object.freeze({
21
24
  config: ".forgeloop/config.json",
22
25
  sources: ".forgeloop/sources.json",
23
26
  manifest: ".forgeloop/.manifest.json",
24
27
  kit: ".forgeloop/kit",
25
28
  gitignore: ".forgeloop/.gitignore",
29
+ policyDir: ".forgeloop/policy",
30
+ policyRules: ".forgeloop/policy/rules.json",
31
+ policyBaseline: ".forgeloop/policy/baseline.json",
32
+ policyLock: ".forgeloop/policy/policy.lock",
33
+ policyDiscovery: ".forgeloop/policy/discovery.json",
26
34
  });
27
35
 
28
36
  export const LEGACY_TASK_ARTIFACT_PATHS = Object.freeze({
@@ -92,5 +100,6 @@ export function buildTaskArtifactPaths(taskId) {
92
100
  gates: `${dir}/${TASK_ARTIFACT_FILES.gates}`,
93
101
  executions: `${dir}/${TASK_ARTIFACT_FILES.executions}`,
94
102
  lock: `${dir}/${TASK_ARTIFACT_FILES.lock}`,
103
+ policySnapshot: `${dir}/${TASK_ARTIFACT_FILES.policySnapshot}`,
95
104
  });
96
105
  }
@@ -58,6 +58,11 @@ export const TEMPLATE_PATHS = [
58
58
  "schemas/event.schema.json",
59
59
  "schemas/activation.schema.json",
60
60
  "schemas/policy.schema.json",
61
+ "schemas/policy-rules.schema.json",
62
+ "schemas/policy-discovery.schema.json",
63
+ "schemas/policy-baseline.schema.json",
64
+ "schemas/policy-lock.schema.json",
65
+ "schemas/policy-snapshot.schema.json",
61
66
  "schemas/task-bundle.schema.json",
62
67
  "schemas/authority.schema.json",
63
68
  "schemas/task-descriptor.schema.json",