@nebulacomponents/citable 1.3.1 → 1.5.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 (181) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +49 -9
  3. package/dist/universal/.agents/skills/citable/SKILL.md +14 -3
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/commands/README.md +8 -2
  6. package/dist/universal/.agents/skills/citable/commands/measurement.md +22 -5
  7. package/dist/universal/.agents/skills/citable/commands/observe-and-act.md +40 -0
  8. package/dist/universal/.agents/skills/citable/manifest.json +15 -8
  9. package/dist/universal/.agents/skills/citable/references/capability-boundaries.md +8 -8
  10. package/dist/universal/.agents/skills/citable/schemas/connection.schema.json +32 -0
  11. package/dist/universal/.agents/skills/citable/schemas/intervention.schema.json +33 -0
  12. package/dist/universal/.agents/skills/citable/schemas/metric.schema.json +42 -0
  13. package/dist/universal/.agents/skills/citable/schemas/objective.schema.json +65 -0
  14. package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +20 -0
  15. package/dist/universal/.agents/skills/citable/schemas/remediation-spec.schema.json +28 -0
  16. package/dist/universal/.claude/skills/citable/SKILL.md +14 -3
  17. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  18. package/dist/universal/.claude/skills/citable/commands/README.md +8 -2
  19. package/dist/universal/.claude/skills/citable/commands/measurement.md +22 -5
  20. package/dist/universal/.claude/skills/citable/commands/observe-and-act.md +40 -0
  21. package/dist/universal/.claude/skills/citable/manifest.json +15 -8
  22. package/dist/universal/.claude/skills/citable/references/capability-boundaries.md +8 -8
  23. package/dist/universal/.claude/skills/citable/schemas/connection.schema.json +32 -0
  24. package/dist/universal/.claude/skills/citable/schemas/intervention.schema.json +33 -0
  25. package/dist/universal/.claude/skills/citable/schemas/metric.schema.json +42 -0
  26. package/dist/universal/.claude/skills/citable/schemas/objective.schema.json +65 -0
  27. package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +20 -0
  28. package/dist/universal/.claude/skills/citable/schemas/remediation-spec.schema.json +28 -0
  29. package/dist/universal/.cursor/skills/citable/SKILL.md +14 -3
  30. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  31. package/dist/universal/.cursor/skills/citable/commands/README.md +8 -2
  32. package/dist/universal/.cursor/skills/citable/commands/measurement.md +22 -5
  33. package/dist/universal/.cursor/skills/citable/commands/observe-and-act.md +40 -0
  34. package/dist/universal/.cursor/skills/citable/manifest.json +15 -8
  35. package/dist/universal/.cursor/skills/citable/references/capability-boundaries.md +8 -8
  36. package/dist/universal/.cursor/skills/citable/schemas/connection.schema.json +32 -0
  37. package/dist/universal/.cursor/skills/citable/schemas/intervention.schema.json +33 -0
  38. package/dist/universal/.cursor/skills/citable/schemas/metric.schema.json +42 -0
  39. package/dist/universal/.cursor/skills/citable/schemas/objective.schema.json +65 -0
  40. package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +20 -0
  41. package/dist/universal/.cursor/skills/citable/schemas/remediation-spec.schema.json +28 -0
  42. package/dist/universal/.gemini/skills/citable/SKILL.md +14 -3
  43. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  44. package/dist/universal/.gemini/skills/citable/commands/README.md +8 -2
  45. package/dist/universal/.gemini/skills/citable/commands/measurement.md +22 -5
  46. package/dist/universal/.gemini/skills/citable/commands/observe-and-act.md +40 -0
  47. package/dist/universal/.gemini/skills/citable/manifest.json +15 -8
  48. package/dist/universal/.gemini/skills/citable/references/capability-boundaries.md +8 -8
  49. package/dist/universal/.gemini/skills/citable/schemas/connection.schema.json +32 -0
  50. package/dist/universal/.gemini/skills/citable/schemas/intervention.schema.json +33 -0
  51. package/dist/universal/.gemini/skills/citable/schemas/metric.schema.json +42 -0
  52. package/dist/universal/.gemini/skills/citable/schemas/objective.schema.json +65 -0
  53. package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +20 -0
  54. package/dist/universal/.gemini/skills/citable/schemas/remediation-spec.schema.json +28 -0
  55. package/dist/universal/.github/skills/citable/SKILL.md +14 -3
  56. package/dist/universal/.github/skills/citable/VERSION +1 -1
  57. package/dist/universal/.github/skills/citable/commands/README.md +8 -2
  58. package/dist/universal/.github/skills/citable/commands/measurement.md +22 -5
  59. package/dist/universal/.github/skills/citable/commands/observe-and-act.md +40 -0
  60. package/dist/universal/.github/skills/citable/manifest.json +15 -8
  61. package/dist/universal/.github/skills/citable/references/capability-boundaries.md +8 -8
  62. package/dist/universal/.github/skills/citable/schemas/connection.schema.json +32 -0
  63. package/dist/universal/.github/skills/citable/schemas/intervention.schema.json +33 -0
  64. package/dist/universal/.github/skills/citable/schemas/metric.schema.json +42 -0
  65. package/dist/universal/.github/skills/citable/schemas/objective.schema.json +65 -0
  66. package/dist/universal/.github/skills/citable/schemas/observation.schema.json +20 -0
  67. package/dist/universal/.github/skills/citable/schemas/remediation-spec.schema.json +28 -0
  68. package/dist/universal/.kiro/skills/citable/SKILL.md +14 -3
  69. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  70. package/dist/universal/.kiro/skills/citable/commands/README.md +8 -2
  71. package/dist/universal/.kiro/skills/citable/commands/measurement.md +22 -5
  72. package/dist/universal/.kiro/skills/citable/commands/observe-and-act.md +40 -0
  73. package/dist/universal/.kiro/skills/citable/manifest.json +15 -8
  74. package/dist/universal/.kiro/skills/citable/references/capability-boundaries.md +8 -8
  75. package/dist/universal/.kiro/skills/citable/schemas/connection.schema.json +32 -0
  76. package/dist/universal/.kiro/skills/citable/schemas/intervention.schema.json +33 -0
  77. package/dist/universal/.kiro/skills/citable/schemas/metric.schema.json +42 -0
  78. package/dist/universal/.kiro/skills/citable/schemas/objective.schema.json +65 -0
  79. package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +20 -0
  80. package/dist/universal/.kiro/skills/citable/schemas/remediation-spec.schema.json +28 -0
  81. package/dist/universal/.opencode/skills/citable/SKILL.md +14 -3
  82. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  83. package/dist/universal/.opencode/skills/citable/commands/README.md +8 -2
  84. package/dist/universal/.opencode/skills/citable/commands/measurement.md +22 -5
  85. package/dist/universal/.opencode/skills/citable/commands/observe-and-act.md +40 -0
  86. package/dist/universal/.opencode/skills/citable/manifest.json +15 -8
  87. package/dist/universal/.opencode/skills/citable/references/capability-boundaries.md +8 -8
  88. package/dist/universal/.opencode/skills/citable/schemas/connection.schema.json +32 -0
  89. package/dist/universal/.opencode/skills/citable/schemas/intervention.schema.json +33 -0
  90. package/dist/universal/.opencode/skills/citable/schemas/metric.schema.json +42 -0
  91. package/dist/universal/.opencode/skills/citable/schemas/objective.schema.json +65 -0
  92. package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +20 -0
  93. package/dist/universal/.opencode/skills/citable/schemas/remediation-spec.schema.json +28 -0
  94. package/dist/universal/.pi/agent/skills/citable/SKILL.md +14 -3
  95. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  96. package/dist/universal/.pi/agent/skills/citable/commands/README.md +8 -2
  97. package/dist/universal/.pi/agent/skills/citable/commands/measurement.md +22 -5
  98. package/dist/universal/.pi/agent/skills/citable/commands/observe-and-act.md +40 -0
  99. package/dist/universal/.pi/agent/skills/citable/manifest.json +15 -8
  100. package/dist/universal/.pi/agent/skills/citable/references/capability-boundaries.md +8 -8
  101. package/dist/universal/.pi/agent/skills/citable/schemas/connection.schema.json +32 -0
  102. package/dist/universal/.pi/agent/skills/citable/schemas/intervention.schema.json +33 -0
  103. package/dist/universal/.pi/agent/skills/citable/schemas/metric.schema.json +42 -0
  104. package/dist/universal/.pi/agent/skills/citable/schemas/objective.schema.json +65 -0
  105. package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +20 -0
  106. package/dist/universal/.pi/agent/skills/citable/schemas/remediation-spec.schema.json +28 -0
  107. package/dist/universal/.qoder/skills/citable/SKILL.md +14 -3
  108. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  109. package/dist/universal/.qoder/skills/citable/commands/README.md +8 -2
  110. package/dist/universal/.qoder/skills/citable/commands/measurement.md +22 -5
  111. package/dist/universal/.qoder/skills/citable/commands/observe-and-act.md +40 -0
  112. package/dist/universal/.qoder/skills/citable/manifest.json +15 -8
  113. package/dist/universal/.qoder/skills/citable/references/capability-boundaries.md +8 -8
  114. package/dist/universal/.qoder/skills/citable/schemas/connection.schema.json +32 -0
  115. package/dist/universal/.qoder/skills/citable/schemas/intervention.schema.json +33 -0
  116. package/dist/universal/.qoder/skills/citable/schemas/metric.schema.json +42 -0
  117. package/dist/universal/.qoder/skills/citable/schemas/objective.schema.json +65 -0
  118. package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +20 -0
  119. package/dist/universal/.qoder/skills/citable/schemas/remediation-spec.schema.json +28 -0
  120. package/dist/universal/.rovodev/skills/citable/SKILL.md +14 -3
  121. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  122. package/dist/universal/.rovodev/skills/citable/commands/README.md +8 -2
  123. package/dist/universal/.rovodev/skills/citable/commands/measurement.md +22 -5
  124. package/dist/universal/.rovodev/skills/citable/commands/observe-and-act.md +40 -0
  125. package/dist/universal/.rovodev/skills/citable/manifest.json +15 -8
  126. package/dist/universal/.rovodev/skills/citable/references/capability-boundaries.md +8 -8
  127. package/dist/universal/.rovodev/skills/citable/schemas/connection.schema.json +32 -0
  128. package/dist/universal/.rovodev/skills/citable/schemas/intervention.schema.json +33 -0
  129. package/dist/universal/.rovodev/skills/citable/schemas/metric.schema.json +42 -0
  130. package/dist/universal/.rovodev/skills/citable/schemas/objective.schema.json +65 -0
  131. package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +20 -0
  132. package/dist/universal/.rovodev/skills/citable/schemas/remediation-spec.schema.json +28 -0
  133. package/dist/universal/.trae/skills/citable/SKILL.md +14 -3
  134. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  135. package/dist/universal/.trae/skills/citable/commands/README.md +8 -2
  136. package/dist/universal/.trae/skills/citable/commands/measurement.md +22 -5
  137. package/dist/universal/.trae/skills/citable/commands/observe-and-act.md +40 -0
  138. package/dist/universal/.trae/skills/citable/manifest.json +15 -8
  139. package/dist/universal/.trae/skills/citable/references/capability-boundaries.md +8 -8
  140. package/dist/universal/.trae/skills/citable/schemas/connection.schema.json +32 -0
  141. package/dist/universal/.trae/skills/citable/schemas/intervention.schema.json +33 -0
  142. package/dist/universal/.trae/skills/citable/schemas/metric.schema.json +42 -0
  143. package/dist/universal/.trae/skills/citable/schemas/objective.schema.json +65 -0
  144. package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +20 -0
  145. package/dist/universal/.trae/skills/citable/schemas/remediation-spec.schema.json +28 -0
  146. package/dist/universal/.trae-cn/skills/citable/SKILL.md +14 -3
  147. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  148. package/dist/universal/.trae-cn/skills/citable/commands/README.md +8 -2
  149. package/dist/universal/.trae-cn/skills/citable/commands/measurement.md +22 -5
  150. package/dist/universal/.trae-cn/skills/citable/commands/observe-and-act.md +40 -0
  151. package/dist/universal/.trae-cn/skills/citable/manifest.json +15 -8
  152. package/dist/universal/.trae-cn/skills/citable/references/capability-boundaries.md +8 -8
  153. package/dist/universal/.trae-cn/skills/citable/schemas/connection.schema.json +32 -0
  154. package/dist/universal/.trae-cn/skills/citable/schemas/intervention.schema.json +33 -0
  155. package/dist/universal/.trae-cn/skills/citable/schemas/metric.schema.json +42 -0
  156. package/dist/universal/.trae-cn/skills/citable/schemas/objective.schema.json +65 -0
  157. package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +20 -0
  158. package/dist/universal/.trae-cn/skills/citable/schemas/remediation-spec.schema.json +28 -0
  159. package/dist/universal/manifest.json +183 -99
  160. package/package.json +12 -1
  161. package/schemas/connection.schema.json +32 -0
  162. package/schemas/intervention.schema.json +33 -0
  163. package/schemas/metric.schema.json +42 -0
  164. package/schemas/objective.schema.json +65 -0
  165. package/schemas/observation.schema.json +20 -0
  166. package/schemas/remediation-spec.schema.json +28 -0
  167. package/skill/SKILL.md +14 -3
  168. package/skill/commands/README.md +8 -2
  169. package/skill/commands/measurement.md +22 -5
  170. package/skill/commands/observe-and-act.md +40 -0
  171. package/skill/references/capability-boundaries.md +8 -8
  172. package/src/cli/index.js +67 -0
  173. package/src/commands/applyRemediation.js +57 -0
  174. package/src/commands/measurement.js +205 -0
  175. package/src/commands/monitor.js +40 -0
  176. package/src/commands/observe.js +235 -0
  177. package/src/evidence/run.js +1 -1
  178. package/src/installer/index.js +35 -7
  179. package/src/installer/providers.js +1 -1
  180. package/src/observations/common.js +61 -0
  181. package/src/registries/index.js +13 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebulacomponents/citable",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "Production-grade SEO, AEO, and GEO agent skill with auditable detection, claim governance, evidence validation, and multi-agent installation.",
5
5
  "keywords": [
6
6
  "seo",
@@ -52,6 +52,8 @@
52
52
  "test:integration": "node --test --test-concurrency=1 --test-reporter=spec 'tests/integration/**/*.test.js'",
53
53
  "test:e2e": "node --test --test-concurrency=1 'tests/e2e/**/*.test.js'",
54
54
  "build:dist": "node scripts/build-dist.js",
55
+ "release:prepare": "node scripts/release-process.js prepare",
56
+ "release:validate": "node scripts/release-process.js validate",
55
57
  "prepack": "npm run build:dist"
56
58
  },
57
59
  "bin": {
@@ -86,7 +88,16 @@
86
88
  "dependencies": {
87
89
  "ajv": "^8.20.0",
88
90
  "ajv-formats": "^3.0.1",
91
+ "csv-parse": "^7.0.1",
89
92
  "js-yaml": "^5.2.1",
90
93
  "node-html-parser": "^9.0.0"
94
+ },
95
+ "peerDependencies": {
96
+ "playwright": ">=1.50.0"
97
+ },
98
+ "peerDependenciesMeta": {
99
+ "playwright": {
100
+ "optional": true
101
+ }
91
102
  }
92
103
  }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$id": "citable://schemas/connection.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Optional connector state registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "connections" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["connection_id", "provider", "state", "authentication", "scopes", "limitations"],
18
+ "properties": {
19
+ "connection_id": { "type": "string", "pattern": "^CONNECTION-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "provider": { "type": "string", "minLength": 1 },
21
+ "property_id": { "type": ["string", "null"] },
22
+ "state": { "type": "string", "enum": ["not_configured", "configured", "synchronized", "partial", "authorization_expired", "permission_denied", "quota_limited", "temporarily_unavailable"] },
23
+ "authentication": { "type": "string", "enum": ["none", "environment", "os_keychain", "external_secret_manager", "owner_import"] },
24
+ "scopes": { "type": "array", "items": { "type": "string" } },
25
+ "last_synchronized_at": { "type": ["string", "null"], "format": "date-time" },
26
+ "cursor": { "type": ["string", "null"] },
27
+ "limitations": { "type": "array", "items": { "type": "string" } }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$id": "citable://schemas/intervention.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Intervention registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "interventions" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["intervention_id", "name", "status", "deployed_at", "affected_urls", "finding_ids", "objective_ids", "limitations"],
18
+ "properties": {
19
+ "intervention_id": { "type": "string", "pattern": "^INTERVENTION-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "name": { "type": "string", "minLength": 1 },
21
+ "status": { "type": "string", "enum": ["planned", "deployed", "rolled_back", "superseded"] },
22
+ "deployed_at": { "type": ["string", "null"], "format": "date-time" },
23
+ "commit": { "type": ["string", "null"] },
24
+ "pull_request": { "type": ["string", "null"] },
25
+ "affected_urls": { "type": "array", "items": { "type": "string" } },
26
+ "finding_ids": { "type": "array", "items": { "type": "string" } },
27
+ "objective_ids": { "type": "array", "items": { "type": "string", "pattern": "^OBJECTIVE-" } },
28
+ "limitations": { "type": "array", "items": { "type": "string" } }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$id": "citable://schemas/metric.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Metric registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "metrics" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["metric_id", "name", "provider", "external_name", "unit", "value_type", "aggregation", "direction", "dimensions", "limitations"],
18
+ "properties": {
19
+ "metric_id": { "type": "string", "pattern": "^METRIC-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "name": { "type": "string", "minLength": 1 },
21
+ "description": { "type": "string" },
22
+ "provider": { "type": "string", "minLength": 1 },
23
+ "external_name": { "type": "string", "minLength": 1 },
24
+ "unit": { "type": "string", "enum": ["count", "ratio", "percent", "currency", "duration_ms", "position", "score", "custom"] },
25
+ "custom_unit": { "type": "string", "minLength": 1 },
26
+ "value_type": { "type": "string", "enum": ["integer", "number"] },
27
+ "aggregation": { "type": "string", "enum": ["sum", "average", "minimum", "maximum", "latest", "provider_defined"] },
28
+ "direction": { "type": "string", "enum": ["increase", "decrease", "maintain", "contextual"] },
29
+ "dimensions": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
30
+ "limitations": { "type": "array", "items": { "type": "string", "minLength": 1 } },
31
+ "status": { "type": "string", "enum": ["active", "deprecated"] }
32
+ },
33
+ "allOf": [
34
+ {
35
+ "if": { "properties": { "unit": { "const": "custom" } } },
36
+ "then": { "required": ["custom_unit"] }
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "$id": "citable://schemas/objective.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Objective registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "objectives" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["objective_id", "name", "status", "primary_metrics", "supporting_metrics", "guardrails", "cohort", "comparison"],
18
+ "properties": {
19
+ "objective_id": { "type": "string", "pattern": "^OBJECTIVE-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "name": { "type": "string", "minLength": 1 },
21
+ "description": { "type": "string" },
22
+ "owner": { "type": ["string", "null"] },
23
+ "status": { "type": "string", "enum": ["draft", "active", "paused", "archived"] },
24
+ "primary_metrics": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/definitions/metricRef" } },
25
+ "supporting_metrics": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/definitions/metricRef" } },
26
+ "guardrails": { "type": "array", "items": { "$ref": "#/definitions/guardrail" } },
27
+ "cohort": { "$ref": "#/definitions/cohort" },
28
+ "comparison": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["baseline_days", "evaluation_days", "minimum_observations"],
32
+ "properties": {
33
+ "baseline_days": { "type": "integer", "minimum": 1 },
34
+ "evaluation_days": { "type": "integer", "minimum": 1 },
35
+ "minimum_observations": { "type": "integer", "minimum": 1 }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ },
42
+ "definitions": {
43
+ "metricRef": { "type": "string", "pattern": "^METRIC-[A-Z0-9][A-Z0-9_-]*$" },
44
+ "guardrail": {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": ["metric_id", "operator", "threshold"],
48
+ "properties": {
49
+ "metric_id": { "$ref": "#/definitions/metricRef" },
50
+ "operator": { "type": "string", "enum": ["gte", "lte", "increase_by", "decrease_by", "no_regression"] },
51
+ "threshold": { "type": "number" }
52
+ }
53
+ },
54
+ "cohort": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["urls", "queries", "labels"],
58
+ "properties": {
59
+ "urls": { "type": "array", "items": { "type": "string" } },
60
+ "queries": { "type": "array", "items": { "type": "string" } },
61
+ "labels": { "type": "array", "items": { "type": "string" } }
62
+ }
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "$id": "citable://schemas/observation.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Collected observation envelope",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["observation_id", "kind", "state", "collected_at", "collection_method", "confidence", "source", "evidence_hash", "data"],
8
+ "properties": {
9
+ "observation_id": { "type": "string", "minLength": 1 },
10
+ "kind": { "type": "string", "enum": ["render", "index", "citation", "citation_review", "crawler_log", "passage", "canonical_freshness", "performance", "corroboration", "metric"] },
11
+ "state": { "type": "string", "enum": ["observed", "not_observed", "not_evidenced", "incomplete", "review_required", "failed"] },
12
+ "collected_at": { "type": "string", "format": "date-time" },
13
+ "collection_method": { "type": "string", "enum": ["live_api", "browser", "owner_import", "synthetic_fetch", "static_analysis", "human_review"] },
14
+ "confidence": { "type": "string", "enum": ["confirmed", "high", "medium", "low", "unknown"] },
15
+ "source": { "type": "string", "minLength": 1 },
16
+ "evidence_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
17
+ "data": { "type": "object" },
18
+ "limitations": { "type": "array", "items": { "type": "string" } }
19
+ }
20
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$id": "citable://schemas/remediation-spec.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Guarded remediation specification",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["source_run_id", "operations"],
8
+ "properties": {
9
+ "source_run_id": { "type": "string" },
10
+ "operations": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "additionalProperties": false,
15
+ "required": ["operation_id", "file", "find", "replace", "expected_file_hash", "finding_ids"],
16
+ "properties": {
17
+ "operation_id": { "type": "string" },
18
+ "file": { "type": "string", "minLength": 1 },
19
+ "find": { "type": "string", "minLength": 1 },
20
+ "replace": { "type": "string" },
21
+ "expected_file_hash": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
22
+ "finding_ids": { "type": "array", "minItems": 1, "items": { "type": "string" } },
23
+ "reviewer": { "type": "string" }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ }
package/skill/SKILL.md CHANGED
@@ -7,7 +7,7 @@ description: >
7
7
  generative engine optimization, structured data governance, claim substantiation,
8
8
  crawler policy, entity consistency, content discoverability, or wants a site audited
9
9
  for how search and AI systems will retrieve, understand, cite, or recommend it.
10
- version: 1.3.1
10
+ version: 1.5.0
11
11
  ---
12
12
 
13
13
  # Citable — search & generative discoverability governance
@@ -85,6 +85,13 @@ citable schema --target <dir|url>
85
85
  citable validate [registries|claims|evidence|schema|links]
86
86
  citable compare-snapshots [runA runB]
87
87
  citable action-plan [run-id] # ordered actions, blockers, semantic gates, verification
88
+ citable observe <mode> [options] # render/index/citation/log/passage/consensus/performance evidence
89
+ citable apply --input <spec> [--write] # reviewed, hash-locked remediation; dry run by default
90
+ citable monitor [runA runB] # observation regression alerts
91
+ citable metrics import --provider <name> --input <csv|json>
92
+ citable objectives init --input <json|yaml> [--write]
93
+ citable objectives validate
94
+ citable evaluate [objective-id] [--ref-date YYYY-MM-DD]
88
95
  ```
89
96
 
90
97
  Audit scopes: `technical seo aeo geo architecture entity claims evidence schema
@@ -126,6 +133,9 @@ mandatory semantic reviews make the relevant posture `not_established`.
126
133
  Consult `references/capability-boundaries.md` before claiming any observation;
127
134
  it identifies which states the current CLI can collect and which require
128
135
  operator data or future adapters.
136
+ Use `commands/observe-and-act.md` for collector prerequisites, evidence labels,
137
+ the controlled citation adapter protocol, remediation refusal conditions, and
138
+ monitoring interpretation.
129
139
 
130
140
  ## Command workflows
131
141
 
@@ -138,8 +148,9 @@ mandatory.
138
148
 
139
149
  ## Registries are the source of truth
140
150
 
141
- `.citable/` holds nine YAML registries (queries, prompts, entities, claims,
142
- evidence, pages, crawlers, competitors, experiments), all schema-validated
151
+ `.citable/` holds YAML registries for queries, prompts, entities, claims,
152
+ evidence, pages, crawlers, competitors, experiments, metrics, objectives,
153
+ interventions, and optional connection state, all schema-validated
143
154
  (`schemas/*.schema.json`) with referential integrity checks. Rules:
144
155
 
145
156
  - Never overwrite registry content without history — use the loader/saver in
@@ -21,11 +21,17 @@ intent, not executability, so statuses are strict:
21
21
  | /citable validate, validate-claims, validate-evidence, validate-schema, validate-links | implemented |
22
22
  | /citable compare-snapshots / validate-regression | implemented |
23
23
  | /citable action-plan | implemented — writes ordered, source-run-bound action artifacts outside immutable evidence packages |
24
+ | /citable observe | implemented — render, index, citation, crawler-log, passage, consensus, performance, and corroboration evidence |
25
+ | /citable apply | implemented — reviewed, hash-locked source replacements; dry run by default |
26
+ | /citable monitor | implemented — compares immutable observation runs and emits evidence-linked alerts |
27
+ | /citable metrics import | implemented — validates declared CSV/JSON metrics and writes immutable observations |
28
+ | /citable objectives init / validate | implemented — user-owned metric selection, cohorts, windows, and guardrails |
29
+ | /citable evaluate | implemented — independent baseline/evaluation comparisons with inconclusive handling |
24
30
  | /citable ingest, map-site, map-queries, map-prompts, map-entities, map-evidence | specified (ingest.md) |
25
31
  | /citable optimize-page | specified (optimize-page.md) — requires source-to-render mapping, claim-preserving rewrites, build execution, rollback; none of that is proven yet |
26
32
  | /citable create-page, answer-block, architect, interlink, consolidate, metadata | specified (page-work.md) |
27
- | /citable validate-render | specified (validate-render.md) blocked on the rendered-truth phase (docs/detectors/render-phase-plan.md) |
28
- | /citable measure seo/aeo/geo, test-prompts, monitor-crawlers, monitor-contradictions | specified (measurement.md) no external engine adapters exist or are claimed |
33
+ | /citable validate-render | partially implemented by `observe render`; mobile/cross-browser and interaction exploration remain |
34
+ | /citable measure seo/aeo/geo, test-prompts, monitor-crawlers, monitor-contradictions | partially implemented by metric/observation imports, objective evaluation, Google/CrUX live APIs, custom citation adapters, and monitor comparisons |
29
35
 
30
36
  Shared refusal conditions (all commands): any action that would fabricate
31
37
  facts, citations, evidence, authorship, reviews, corroboration, or hidden
@@ -1,14 +1,31 @@
1
1
  ---
2
2
  command: /citable measure seo|aeo|geo, test-prompts, monitor-crawlers, monitor-contradictions
3
- purpose: Observation and measurement workflows. Citable records and validates observations; it does not claim live engine adapters it does not have.
3
+ purpose: Observation and measurement workflows with exact collector and product-mode boundaries.
4
4
  ---
5
5
 
6
6
  # Honest capability statement
7
7
 
8
- This MVP ships **no external engine adapters** (no Search Console API client,
9
- no ChatGPT/Perplexity automation). Measurement commands therefore operate on
10
- *operator-supplied observations*, validated against the data contracts, plus
11
- registry-side integrity checks (MEAS-001/002/003). Do not imply otherwise.
8
+ Citable ships Google URL Inspection, CrUX field collection, normalized owner
9
+ imports, and a disclosed custom citation-adapter protocol. It does not claim
10
+ Bing AI Performance automation or equivalence between an API adapter and a
11
+ consumer answer product. Preserve collector and product mode on every result.
12
+
13
+ # implemented metric and objective workflow
14
+
15
+ `citable metrics import --provider <name> --input <csv|json>` validates every
16
+ row against a declared entry in `metrics.yaml` and writes immutable metric
17
+ observations. `citable objectives init --input <json|yaml>` is dry-run by
18
+ default; `--write` adds the validated objective with registry history.
19
+ `citable objectives validate` checks contracts and metric references.
20
+ `citable evaluate [objective-id]` compares independent baseline and evaluation
21
+ windows, deduplicates repeated evidence, and reports each metric and guardrail
22
+ separately. Insufficient data is `inconclusive`, never zero. Results describe
23
+ temporal association and do not establish causation.
24
+
25
+ Metrics, objectives, interventions, and connection state are optional. The
26
+ absence of provider credentials is `not_configured`, not a finding and not
27
+ evidence of zero activity. Credentials must never enter registries or evidence
28
+ packages.
12
29
 
13
30
  # measure seo
14
31
  Inputs the operator exports (Search Console/Bing/analytics CSV or JSON).
@@ -0,0 +1,40 @@
1
+ ---
2
+ command: /citable observe|apply|monitor
3
+ purpose: Collect external and rendered evidence, apply reviewed source changes, and detect regressions without collapsing unlike evidence states.
4
+ failure_behaviour: unavailable dependency, credential, source run, reviewer, exact hash, or unique replacement -> incomplete or fail; never infer success
5
+ ---
6
+
7
+ # Observe, apply, and monitor
8
+
9
+ ## Collection modes
10
+
11
+ - `observe render --target <URL>` uses optional Playwright. Browser evidence is
12
+ not search-index evidence.
13
+ - `observe index --input <JSON>` imports owner exports. With `--target`,
14
+ `--site-url`, and `GSC_ACCESS_TOKEN`, it calls Google URL Inspection, which
15
+ reports the indexed version rather than performing a live indexability test.
16
+ - `observe citations --input <JSON>` imports complete controlled observations.
17
+ With `--endpoint`, the input is a versioned prompt corpus and each prompt runs
18
+ 1-20 times (default 3) through the disclosed adapter protocol. An API is not
19
+ presumed equivalent to a provider's consumer search experience.
20
+ - `observe logs --input <JSON>` imports server/CDN records. User-agent matches
21
+ without IP verification remain low confidence.
22
+ - `observe passages|consensus --target <dir|URL>` performs static extraction.
23
+ - `observe performance` imports evidence or calls CrUX with `CRUX_API_KEY`.
24
+ - `observe corroboration --input <JSON>` records observable external mentions;
25
+ controlled or unverified sources do not become authority claims.
26
+
27
+ ## Guarded remediation
28
+
29
+ `apply --input remediation-spec.json` is a dry run. Every operation binds to an
30
+ existing audit run and finding IDs, stays inside repository source, names a
31
+ reviewer, matches the exact file hash, and identifies exactly one source string.
32
+ `--write` applies only after all operations validate. Build, tests, semantic
33
+ review, re-audit, and snapshot comparison remain mandatory afterward.
34
+
35
+ ## Monitoring
36
+
37
+ `monitor [runA runB]` reports state changes, missing observations, index loss,
38
+ canonical disagreement, and citation-presence changes. Alerts are evidence
39
+ changes, not causal explanations or ranking conclusions. Schedule it in CI at
40
+ the cadence defined by lifecycle and experiment records.
@@ -6,18 +6,18 @@ Use the narrowest supported state. Never promote evidence across rows.
6
6
  | --- | --- | --- |
7
7
  | Allowed by policy | Implemented | Effective robots rule plus crawler-purpose registry decision |
8
8
  | Synthetic fetch succeeded | Partial | Captured status, redirects, headers, body from the declared user agent; label identity simulated |
9
- | Verified crawler reached edge | Operator supplied | Request log plus provider-published IP verification where supported |
10
- | Observed in production logs | Operator supplied | Timestamped server/CDN log with URL, status, bytes, latency, cache result, UA, and verification result |
11
- | Indexed | Operator supplied | Search Console/Bing Webmaster/API or timestamped engine observation |
9
+ | Verified crawler reached edge | Imported evidence | `observe logs` record plus provider-published IP verification where supported |
10
+ | Observed in production logs | Imported evidence | Timestamped `observe logs` artifact with URL, status, bytes, latency, cache result, UA, and verification result |
11
+ | Indexed | Google API or imported evidence | `observe index`; Google inspection covers the indexed version, other engines require owner exports |
12
12
  | Returned by retrieval | Operator supplied | Provider observation containing retrieved source URL |
13
- | Cited | Operator supplied | Complete answer observation with citation URL |
14
- | Materially supports answer | Human review required | Answer claim mapped to a passage that entails it |
13
+ | Cited | Import or disclosed adapter | Complete `observe citations` answer with citation URL and product/adapter mode |
14
+ | Materially supports answer | Human review required | `citation_review` maps answer claim to source passage and names the reviewer |
15
15
  | Recommended | Operator supplied + review | Explicit recommendation, constraints, provider/mode/locale/time |
16
16
 
17
- Current CLI URL mode does not prove crawler identity, WAF allowlisting,
17
+ URL audit mode alone does not prove crawler identity, WAF allowlisting,
18
18
  production-log access, index presence, selected canonical, rendered DOM parity,
19
- or provider citation behavior. Record those states as `not_evidenced` unless the
20
- specified evidence is supplied.
19
+ or provider citation behavior. Observation collectors can supply narrower
20
+ evidence for these states; unavailable inputs remain `not_evidenced`.
21
21
 
22
22
  Primary-source lineage for these boundaries:
23
23
 
package/src/cli/index.js CHANGED
@@ -12,6 +12,10 @@ import { compareSnapshots } from '../commands/compareSnapshots.js';
12
12
  import { isInstallerCommand, runInstallerCommand } from '../installer/index.js';
13
13
  import { selfUpgradeCommand, selfUpgradeExitCode } from '../commands/selfUpgrade.js';
14
14
  import { actionPlan } from '../commands/actionPlan.js';
15
+ import { observe } from '../commands/observe.js';
16
+ import { applyRemediation } from '../commands/applyRemediation.js';
17
+ import { monitor } from '../commands/monitor.js';
18
+ import { evaluateObjective, importMetrics, initializeObjective, validateObjectives } from '../commands/measurement.js';
15
19
 
16
20
  const HELP = `citable — SEO / AEO / GEO audit, remediation, validation, and governance
17
21
 
@@ -34,12 +38,27 @@ Commands
34
38
  validate [mode] registries (default) | claims | evidence | schema | links
35
39
  compare-snapshots [a b] Regression diff between two audit runs
36
40
  action-plan [run] Turn audit findings into ordered remediation work
41
+ observe <mode> Collect render, index, citation, log, passage,
42
+ consensus, performance, or corroboration evidence
43
+ apply Apply a reviewed, hash-locked remediation spec
44
+ monitor [runA runB] Compare observation runs and emit regression alerts
45
+ metrics import Import declared metric observations from CSV/JSON
46
+ objectives init Validate/add one objective from --input (--write to save)
47
+ objectives validate Validate objective contracts and metric references
48
+ evaluate [objective-id] Compare objective baseline and evaluation windows
37
49
  self-upgrade Check for a newer version and upgrade the npx cache
38
50
 
39
51
  Options
40
52
  --target <dir|url> Built output directory or deployed URL to audit
41
53
  --base-url <url> Base URL for path resolution of a built output dir
42
54
  --ref-date <YYYY-MM-DD> Reference date for expiry/staleness checks (default: today)
55
+ --input <file> Import file or remediation specification
56
+ --provider <name> Provider label for imported observations
57
+ --api-key <key> API key (prefer provider environment variables)
58
+ --site-url <property> Search Console property for live URL inspection
59
+ --access-token <token> OAuth token (prefer provider environment variables)
60
+ --endpoint <url> Controlled citation adapter endpoint
61
+ --repeat <count> Repetitions per prompt for citation experiments
43
62
  --write Persist registry changes (map-claims, substantiate)
44
63
  --json Machine-readable output only
45
64
 
@@ -55,6 +74,14 @@ function parseArgs(argv) {
55
74
  else if (a === '--target') args.target = argv[++i];
56
75
  else if (a === '--base-url') args.baseUrl = argv[++i];
57
76
  else if (a === '--ref-date') args.refDate = argv[++i];
77
+ else if (a === '--input') args.input = argv[++i];
78
+ else if (a === '--provider') args.provider = argv[++i];
79
+ else if (a === '--api-key') args.apiKey = argv[++i];
80
+ else if (a === '--site-url') args.siteUrl = argv[++i];
81
+ else if (a === '--access-token') args.accessToken = argv[++i];
82
+ else if (a === '--endpoint') args.endpoint = argv[++i];
83
+ else if (a === '--repeat') args.repeat = Number(argv[++i]);
84
+ else if (a === '--timeout') args.timeout = Number(argv[++i]);
58
85
  else if (a === '--force') args.force = true;
59
86
  else args._.push(a);
60
87
  }
@@ -128,6 +155,46 @@ export async function main(argv = process.argv.slice(2), options = {}) {
128
155
  out(args, `action-plan: ${r.summary.total_actions} action(s) [ready:${r.summary.ready} blocked:${r.summary.blocked}]\nPlan: ${path.join(r.dir, 'action-plan.md')}\nSource audit: ${r.source_run_id}`, r);
129
156
  break;
130
157
  }
158
+ case 'observe': {
159
+ const mode = args._[0];
160
+ const r = await observe(root, mode, args);
161
+ out(args, `observe ${mode}: ${r.summary.total} observation(s) [${Object.entries(r.summary.by_state).map(([k, v]) => `${k}:${v}`).join(' ')}]\nEvidence package: ${r.dir}\nStatus: ${r.manifest.status}`, r);
162
+ break;
163
+ }
164
+ case 'apply': {
165
+ const r = applyRemediation(root, args);
166
+ out(args, `apply: ${r.operations.length} operation(s) ${r.write ? 'applied' : 'proposed (dry run)'}\nEvidence package: ${r.dir}\nSource audit: ${r.source_run_id}`, r);
167
+ break;
168
+ }
169
+ case 'monitor': {
170
+ const r = monitor(root, { runA: args._[0], runB: args._[1] });
171
+ out(args, `monitor ${r.run_a} → ${r.run_b}: ${r.summary.alerts} alert(s), ${r.summary.critical_or_high} critical/high\nReport: ${path.join(r.dir, 'latest.json')}`, r);
172
+ if (r.summary.critical_or_high > 0) process.exitCode = 1;
173
+ break;
174
+ }
175
+ case 'metrics': {
176
+ if (args._[0] !== 'import') throw new Error('usage: citable metrics import --provider <name> --input <csv|json>');
177
+ const r = importMetrics(root, { input: args.input, provider: args.provider });
178
+ out(args, `metrics import: ${r.summary.total} observation(s) from ${args.provider}\nEvidence package: ${r.dir}`, r);
179
+ break;
180
+ }
181
+ case 'objectives': {
182
+ const mode = args._[0];
183
+ if (mode === 'init') {
184
+ const r = initializeObjective(root, { input: args.input, write: args.write });
185
+ out(args, `objectives init: ${r.objective.objective_id} ${r.written ? 'written' : 'valid (dry run; use --write to save)'}`, r);
186
+ } else if (mode === 'validate') {
187
+ const r = validateObjectives(root);
188
+ out(args, `objectives validate: ${r.ok ? 'OK' : 'PROBLEMS'} (${r.count} objective(s))${r.problems.length ? `\n${r.problems.map((problem) => ` - ${problem}`).join('\n')}` : ''}`, r);
189
+ if (!r.ok) process.exitCode = 1;
190
+ } else throw new Error('usage: citable objectives <init|validate> [--input <json|yaml>] [--write]');
191
+ break;
192
+ }
193
+ case 'evaluate': {
194
+ const r = evaluateObjective(root, { objectiveId: args._[0], refDate: args.refDate });
195
+ out(args, `evaluate ${r.objective_id}: ${r.status}\n` + r.metrics.map((metric) => ` ${metric.metric_id}: ${metric.state}${metric.state === 'observed' ? ` (${metric.baseline} → ${metric.evaluation})` : ''}`).join('\n') + `\n${r.interpretation}`, r);
196
+ break;
197
+ }
131
198
  case 'self-upgrade': {
132
199
  const output = await selfUpgradeCommand(argv.slice(1));
133
200
  console.log(output);
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createRun } from '../evidence/run.js';
4
+ import { readJson, sha256 } from '../shared/io.js';
5
+ import { validateAgainst } from '../shared/schemaValidator.js';
6
+
7
+ function inside(root, candidate) {
8
+ const base = path.resolve(root) + path.sep;
9
+ const resolved = path.resolve(root, candidate);
10
+ if (!resolved.startsWith(base) || resolved.includes(`${path.sep}.git${path.sep}`) || resolved.includes(`${path.sep}.citable${path.sep}`)) {
11
+ throw new Error(`remediation file escapes editable source boundary: ${candidate}`);
12
+ }
13
+ return resolved;
14
+ }
15
+
16
+ export function applyRemediation(root, { input, write = false } = {}) {
17
+ if (!input || !fs.existsSync(input)) throw new Error('apply requires --input <remediation-spec.json>');
18
+ const raw = fs.readFileSync(input, 'utf8');
19
+ const spec = readJson(input);
20
+ const valid = validateAgainst('remediation-spec.schema.json', spec);
21
+ if (!valid.valid) throw new Error(`remediation spec invalid: ${valid.errors.join('; ')}`);
22
+ const manifestFile = path.join(root, '.citable', 'runs', spec.source_run_id, 'manifest.json');
23
+ if (!fs.existsSync(manifestFile)) throw new Error(`source run not found: ${spec.source_run_id}`);
24
+ const findingsFile = path.join(root, '.citable', 'runs', spec.source_run_id, 'findings.json');
25
+ if (!fs.existsSync(findingsFile)) throw new Error(`source run has no findings: ${spec.source_run_id}`);
26
+ const sourceFindingIds = new Set(readJson(findingsFile).map((finding) => finding.finding_id));
27
+ const results = [];
28
+ const pending = [];
29
+ for (const operation of spec.operations) {
30
+ const unknownFindings = operation.finding_ids.filter((id) => !sourceFindingIds.has(id));
31
+ if (unknownFindings.length) throw new Error(`operation ${operation.operation_id} references findings outside source run: ${unknownFindings.join(', ')}`);
32
+ const file = inside(root, operation.file);
33
+ if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`remediation target is not a file: ${operation.file}`);
34
+ const before = fs.readFileSync(file, 'utf8');
35
+ if (sha256(before) !== operation.expected_file_hash) throw new Error(`stale remediation refused for ${operation.file}: file hash changed`);
36
+ if (!operation.reviewer) throw new Error(`reviewer required for operation ${operation.operation_id}`);
37
+ const matches = before.split(operation.find).length - 1;
38
+ if (matches !== 1) throw new Error(`operation ${operation.operation_id} expected exactly one match, observed ${matches}`);
39
+ const after = before.replace(operation.find, operation.replace);
40
+ pending.push({ file, before, after });
41
+ results.push({ operation_id: operation.operation_id, file: operation.file, finding_ids: operation.finding_ids, reviewer: operation.reviewer, before_hash: sha256(before), after_hash: sha256(after), status: write ? 'applied' : 'proposed' });
42
+ }
43
+ if (write) {
44
+ const written = [];
45
+ try {
46
+ for (const change of pending) { fs.writeFileSync(change.file, change.after); written.push(change); }
47
+ } catch (error) {
48
+ for (const change of written.reverse()) fs.writeFileSync(change.file, change.before);
49
+ throw new Error(`remediation write failed and prior writes were rolled back: ${error.message}`);
50
+ }
51
+ }
52
+ const run = createRun(root, { command: 'apply', argv: process.argv.slice(2), target: { kind: 'source', location: root, environment: 'local' } });
53
+ run.addInput('remediation_spec', raw);
54
+ run.writeArtifact('remediation/results.json', { source_run_id: spec.source_run_id, write, operations: results });
55
+ run.manifest.warnings.push(write ? 'Source changes applied; build, test, re-audit, and snapshot comparison remain required.' : 'Dry run only; no source files were changed.');
56
+ return { runId: run.runId, dir: run.finalize('completed_with_warnings'), source_run_id: spec.source_run_id, write, operations: results };
57
+ }