@nebulacomponents/citable 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +34 -6
- package/dist/universal/.agents/skills/citable/SKILL.md +8 -3
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/commands/README.md +4 -1
- package/dist/universal/.agents/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.agents/skills/citable/manifest.json +12 -8
- package/dist/universal/.agents/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.agents/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.agents/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.agents/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.claude/skills/citable/SKILL.md +8 -3
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/commands/README.md +4 -1
- package/dist/universal/.claude/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.claude/skills/citable/manifest.json +12 -8
- package/dist/universal/.claude/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.claude/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.claude/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.claude/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.cursor/skills/citable/SKILL.md +8 -3
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/commands/README.md +4 -1
- package/dist/universal/.cursor/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.cursor/skills/citable/manifest.json +12 -8
- package/dist/universal/.cursor/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.cursor/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.cursor/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.cursor/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.gemini/skills/citable/SKILL.md +8 -3
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/commands/README.md +4 -1
- package/dist/universal/.gemini/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.gemini/skills/citable/manifest.json +12 -8
- package/dist/universal/.gemini/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.gemini/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.gemini/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.gemini/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.github/skills/citable/SKILL.md +8 -3
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/commands/README.md +4 -1
- package/dist/universal/.github/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.github/skills/citable/manifest.json +12 -8
- package/dist/universal/.github/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.github/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.github/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.github/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.github/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.kiro/skills/citable/SKILL.md +8 -3
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/commands/README.md +4 -1
- package/dist/universal/.kiro/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.kiro/skills/citable/manifest.json +12 -8
- package/dist/universal/.kiro/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.kiro/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.kiro/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.kiro/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.opencode/skills/citable/SKILL.md +8 -3
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/commands/README.md +4 -1
- package/dist/universal/.opencode/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.opencode/skills/citable/manifest.json +12 -8
- package/dist/universal/.opencode/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.opencode/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.opencode/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.opencode/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +8 -3
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/commands/README.md +4 -1
- package/dist/universal/.pi/agent/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.pi/agent/skills/citable/manifest.json +12 -8
- package/dist/universal/.pi/agent/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.qoder/skills/citable/SKILL.md +8 -3
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/commands/README.md +4 -1
- package/dist/universal/.qoder/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.qoder/skills/citable/manifest.json +12 -8
- package/dist/universal/.qoder/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.qoder/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.qoder/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.qoder/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.rovodev/skills/citable/SKILL.md +8 -3
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/commands/README.md +4 -1
- package/dist/universal/.rovodev/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.rovodev/skills/citable/manifest.json +12 -8
- package/dist/universal/.rovodev/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.rovodev/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.rovodev/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.rovodev/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.trae/skills/citable/SKILL.md +8 -3
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/commands/README.md +4 -1
- package/dist/universal/.trae/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.trae/skills/citable/manifest.json +12 -8
- package/dist/universal/.trae/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.trae/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.trae/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.trae/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +8 -3
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/commands/README.md +4 -1
- package/dist/universal/.trae-cn/skills/citable/commands/measurement.md +17 -0
- package/dist/universal/.trae-cn/skills/citable/manifest.json +12 -8
- package/dist/universal/.trae-cn/skills/citable/schemas/connection.schema.json +32 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/intervention.schema.json +33 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/metric.schema.json +42 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/objective.schema.json +65 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +1 -1
- package/dist/universal/manifest.json +147 -99
- package/package.json +4 -1
- package/schemas/connection.schema.json +32 -0
- package/schemas/intervention.schema.json +33 -0
- package/schemas/metric.schema.json +42 -0
- package/schemas/objective.schema.json +65 -0
- package/schemas/observation.schema.json +1 -1
- package/skill/SKILL.md +8 -3
- package/skill/commands/README.md +4 -1
- package/skill/commands/measurement.md +17 -0
- package/src/cli/index.js +28 -0
- package/src/commands/measurement.js +205 -0
- package/src/installer/index.js +35 -7
- package/src/installer/providers.js +1 -1
- 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
|
+
"version": "1.5.1",
|
|
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,6 +88,7 @@
|
|
|
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"
|
|
91
94
|
},
|
|
@@ -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
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"required": ["observation_id", "kind", "state", "collected_at", "collection_method", "confidence", "source", "evidence_hash", "data"],
|
|
8
8
|
"properties": {
|
|
9
9
|
"observation_id": { "type": "string", "minLength": 1 },
|
|
10
|
-
"kind": { "type": "string", "enum": ["render", "index", "citation", "citation_review", "crawler_log", "passage", "canonical_freshness", "performance", "corroboration"] },
|
|
10
|
+
"kind": { "type": "string", "enum": ["render", "index", "citation", "citation_review", "crawler_log", "passage", "canonical_freshness", "performance", "corroboration", "metric"] },
|
|
11
11
|
"state": { "type": "string", "enum": ["observed", "not_observed", "not_evidenced", "incomplete", "review_required", "failed"] },
|
|
12
12
|
"collected_at": { "type": "string", "format": "date-time" },
|
|
13
13
|
"collection_method": { "type": "string", "enum": ["live_api", "browser", "owner_import", "synthetic_fetch", "static_analysis", "human_review"] },
|
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.
|
|
10
|
+
version: 1.5.1
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Citable — search & generative discoverability governance
|
|
@@ -88,6 +88,10 @@ citable action-plan [run-id] # ordered actions, blockers, semantic gate
|
|
|
88
88
|
citable observe <mode> [options] # render/index/citation/log/passage/consensus/performance evidence
|
|
89
89
|
citable apply --input <spec> [--write] # reviewed, hash-locked remediation; dry run by default
|
|
90
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]
|
|
91
95
|
```
|
|
92
96
|
|
|
93
97
|
Audit scopes: `technical seo aeo geo architecture entity claims evidence schema
|
|
@@ -144,8 +148,9 @@ mandatory.
|
|
|
144
148
|
|
|
145
149
|
## Registries are the source of truth
|
|
146
150
|
|
|
147
|
-
`.citable/` holds
|
|
148
|
-
evidence, pages, crawlers, competitors, experiments
|
|
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
|
|
149
154
|
(`schemas/*.schema.json`) with referential integrity checks. Rules:
|
|
150
155
|
|
|
151
156
|
- Never overwrite registry content without history — use the loader/saver in
|
package/skill/commands/README.md
CHANGED
|
@@ -24,11 +24,14 @@ intent, not executability, so statuses are strict:
|
|
|
24
24
|
| /citable observe | implemented — render, index, citation, crawler-log, passage, consensus, performance, and corroboration evidence |
|
|
25
25
|
| /citable apply | implemented — reviewed, hash-locked source replacements; dry run by default |
|
|
26
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 |
|
|
27
30
|
| /citable ingest, map-site, map-queries, map-prompts, map-entities, map-evidence | specified (ingest.md) |
|
|
28
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 |
|
|
29
32
|
| /citable create-page, answer-block, architect, interlink, consolidate, metadata | specified (page-work.md) |
|
|
30
33
|
| /citable validate-render | partially implemented by `observe render`; mobile/cross-browser and interaction exploration remain |
|
|
31
|
-
| /citable measure seo/aeo/geo, test-prompts, monitor-crawlers, monitor-contradictions | partially implemented by observation imports, Google/CrUX live APIs, custom citation adapters, and monitor comparisons |
|
|
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 |
|
|
32
35
|
|
|
33
36
|
Shared refusal conditions (all commands): any action that would fabricate
|
|
34
37
|
facts, citations, evidence, authorship, reviews, corroboration, or hidden
|
|
@@ -10,6 +10,23 @@ imports, and a disclosed custom citation-adapter protocol. It does not claim
|
|
|
10
10
|
Bing AI Performance automation or equivalence between an API adapter and a
|
|
11
11
|
consumer answer product. Preserve collector and product mode on every result.
|
|
12
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.
|
|
29
|
+
|
|
13
30
|
# measure seo
|
|
14
31
|
Inputs the operator exports (Search Console/Bing/analytics CSV or JSON).
|
|
15
32
|
Workflow: validate segmentation (brand vs non-brand, intent, page type,
|
package/src/cli/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { actionPlan } from '../commands/actionPlan.js';
|
|
|
15
15
|
import { observe } from '../commands/observe.js';
|
|
16
16
|
import { applyRemediation } from '../commands/applyRemediation.js';
|
|
17
17
|
import { monitor } from '../commands/monitor.js';
|
|
18
|
+
import { evaluateObjective, importMetrics, initializeObjective, validateObjectives } from '../commands/measurement.js';
|
|
18
19
|
|
|
19
20
|
const HELP = `citable — SEO / AEO / GEO audit, remediation, validation, and governance
|
|
20
21
|
|
|
@@ -41,6 +42,10 @@ Commands
|
|
|
41
42
|
consensus, performance, or corroboration evidence
|
|
42
43
|
apply Apply a reviewed, hash-locked remediation spec
|
|
43
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
|
|
44
49
|
self-upgrade Check for a newer version and upgrade the npx cache
|
|
45
50
|
|
|
46
51
|
Options
|
|
@@ -167,6 +172,29 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
167
172
|
if (r.summary.critical_or_high > 0) process.exitCode = 1;
|
|
168
173
|
break;
|
|
169
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
|
+
}
|
|
170
198
|
case 'self-upgrade': {
|
|
171
199
|
const output = await selfUpgradeCommand(argv.slice(1));
|
|
172
200
|
console.log(output);
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse as parseCsv } from 'csv-parse/sync';
|
|
4
|
+
import { loadRegistries, saveRegistry } from '../registries/index.js';
|
|
5
|
+
import { envelope, observationRun } from '../observations/common.js';
|
|
6
|
+
import { parseRefDate, readJson, readYaml } from '../shared/io.js';
|
|
7
|
+
import { validateAgainst } from '../shared/schemaValidator.js';
|
|
8
|
+
|
|
9
|
+
function inputDocument(file) {
|
|
10
|
+
if (!file) throw new Error('--input <file> is required');
|
|
11
|
+
if (!fs.existsSync(file)) throw new Error(`input not found: ${file}`);
|
|
12
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
13
|
+
const ext = path.extname(file).toLowerCase();
|
|
14
|
+
if (ext === '.csv') return { raw, rows: parseCsv(raw, { columns: true, skip_empty_lines: true, trim: true }), file: path.resolve(file) };
|
|
15
|
+
const value = ext === '.yaml' || ext === '.yml' ? readYaml(file) : readJson(file);
|
|
16
|
+
return { raw, rows: Array.isArray(value) ? value : value.rows || value.entries || [value], file: path.resolve(file) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function numericValue(raw, type, rowNumber) {
|
|
20
|
+
if (raw === '' || raw == null) throw new Error(`row ${rowNumber}: value is required`);
|
|
21
|
+
const value = Number(raw);
|
|
22
|
+
if (!Number.isFinite(value)) throw new Error(`row ${rowNumber}: value must be numeric`);
|
|
23
|
+
if (type === 'integer' && !Number.isInteger(value)) throw new Error(`row ${rowNumber}: value must be an integer`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isoTime(raw, field, rowNumber) {
|
|
28
|
+
if (!raw || Number.isNaN(new Date(raw).getTime())) throw new Error(`row ${rowNumber}: ${field} must be a valid date-time`);
|
|
29
|
+
return new Date(raw).toISOString();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function importMetrics(root, { input, provider }) {
|
|
33
|
+
if (!provider) throw new Error('metrics import requires --provider <name>');
|
|
34
|
+
const source = inputDocument(input);
|
|
35
|
+
if (!source.rows.length) throw new Error('metric import contains no rows');
|
|
36
|
+
const { registries, problems } = loadRegistries(root);
|
|
37
|
+
if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
|
|
38
|
+
const definitions = new Map(registries.metrics.entries.map((metric) => [metric.metric_id, metric]));
|
|
39
|
+
const observations = source.rows.map((row, index) => {
|
|
40
|
+
const rowNumber = index + 1;
|
|
41
|
+
const metric = definitions.get(row.metric_id);
|
|
42
|
+
if (!metric) throw new Error(`row ${rowNumber}: unknown metric_id ${row.metric_id || '(missing)'}`);
|
|
43
|
+
if (metric.status === 'deprecated') throw new Error(`row ${rowNumber}: metric ${metric.metric_id} is deprecated`);
|
|
44
|
+
if (metric.provider.toLowerCase() !== String(provider).toLowerCase()) {
|
|
45
|
+
throw new Error(`row ${rowNumber}: metric ${metric.metric_id} belongs to ${metric.provider}, not ${provider}`);
|
|
46
|
+
}
|
|
47
|
+
const observedAt = isoTime(row.observed_at || row.date, 'observed_at', rowNumber);
|
|
48
|
+
const dimensions = {};
|
|
49
|
+
for (const dimension of metric.dimensions) {
|
|
50
|
+
if (row[dimension] != null && row[dimension] !== '') dimensions[dimension] = String(row[dimension]);
|
|
51
|
+
}
|
|
52
|
+
const data = {
|
|
53
|
+
metric_id: metric.metric_id,
|
|
54
|
+
provider: metric.provider,
|
|
55
|
+
external_name: metric.external_name,
|
|
56
|
+
value: numericValue(row.value, metric.value_type, rowNumber),
|
|
57
|
+
unit: metric.unit === 'custom' ? metric.custom_unit : metric.unit,
|
|
58
|
+
aggregation: metric.aggregation,
|
|
59
|
+
observed_at: observedAt,
|
|
60
|
+
period_start: row.period_start ? isoTime(row.period_start, 'period_start', rowNumber) : observedAt,
|
|
61
|
+
period_end: row.period_end ? isoTime(row.period_end, 'period_end', rowNumber) : observedAt,
|
|
62
|
+
dimensions,
|
|
63
|
+
source_row: rowNumber,
|
|
64
|
+
};
|
|
65
|
+
if (new Date(data.period_start) > new Date(data.period_end)) throw new Error(`row ${rowNumber}: period_start is after period_end`);
|
|
66
|
+
return envelope('metric', data, {
|
|
67
|
+
method: 'owner_import',
|
|
68
|
+
source: source.file,
|
|
69
|
+
raw: JSON.stringify(row),
|
|
70
|
+
limitations: metric.limitations,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
return observationRun(root, 'metrics import', source.file, observations, { rawInputs: { metric_import: source.raw } });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readObjectiveInput(input) {
|
|
77
|
+
const document = inputDocument(input);
|
|
78
|
+
if (document.rows.length !== 1) throw new Error('objectives init requires exactly one objective');
|
|
79
|
+
return document.rows[0];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function initializeObjective(root, { input, write = false }) {
|
|
83
|
+
const objective = readObjectiveInput(input);
|
|
84
|
+
const { registries, problems } = loadRegistries(root);
|
|
85
|
+
if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
|
|
86
|
+
const candidate = { ...registries.objectives, entries: [...registries.objectives.entries, objective] };
|
|
87
|
+
const check = validateAgainst('objective.schema.json', candidate);
|
|
88
|
+
if (!check.valid) throw new Error(`objective violates contract: ${check.errors.join('; ')}`);
|
|
89
|
+
if (registries.objectives.entries.some((item) => item.objective_id === objective.objective_id)) throw new Error(`objective ${objective.objective_id} already exists`);
|
|
90
|
+
const metricIds = new Set(registries.metrics.entries.map((item) => item.metric_id));
|
|
91
|
+
const refs = [...objective.primary_metrics, ...objective.supporting_metrics, ...objective.guardrails.map((item) => item.metric_id)];
|
|
92
|
+
const unknown = [...new Set(refs.filter((ref) => !metricIds.has(ref)))];
|
|
93
|
+
if (unknown.length) throw new Error(`objective references unknown metrics: ${unknown.join(', ')}`);
|
|
94
|
+
if (write) saveRegistry(root, 'objectives', candidate);
|
|
95
|
+
return { objective, written: write, file: path.join(root, '.citable', 'objectives.yaml') };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateObjectives(root) {
|
|
99
|
+
const { registries, problems } = loadRegistries(root);
|
|
100
|
+
const objectiveProblems = problems.filter((problem) => problem.startsWith('objectives/') || problem.startsWith('objectives.yaml'));
|
|
101
|
+
return { ok: objectiveProblems.length === 0, problems: objectiveProblems, count: registries.objectives.entries.length };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function metricObservations(root) {
|
|
105
|
+
const runs = path.join(root, '.citable', 'runs');
|
|
106
|
+
if (!fs.existsSync(runs)) return [];
|
|
107
|
+
const items = [];
|
|
108
|
+
for (const run of fs.readdirSync(runs)) {
|
|
109
|
+
const dir = path.join(runs, run, 'observations');
|
|
110
|
+
if (!fs.existsSync(dir)) continue;
|
|
111
|
+
for (const file of fs.readdirSync(dir).filter((name) => name.endsWith('-metric.json'))) {
|
|
112
|
+
const item = readJson(path.join(dir, file));
|
|
113
|
+
if (item.kind === 'metric') items.push(item);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return [...new Map(items.map((item) => [item.evidence_hash, item])).values()];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function inCohort(data, cohort) {
|
|
120
|
+
const url = data.dimensions.url || data.dimensions.page || null;
|
|
121
|
+
const query = data.dimensions.query || null;
|
|
122
|
+
const labels = String(data.dimensions.labels || data.dimensions.label || '').split(',').map((label) => label.trim()).filter(Boolean);
|
|
123
|
+
const urlMatch = !cohort.urls.length || (url && cohort.urls.some((pattern) => pattern.endsWith('*') ? url.startsWith(pattern.slice(0, -1)) : url === pattern));
|
|
124
|
+
const queryMatch = !cohort.queries.length || (query && cohort.queries.includes(query));
|
|
125
|
+
const labelMatch = !cohort.labels.length || cohort.labels.some((label) => labels.includes(label));
|
|
126
|
+
return urlMatch && queryMatch && labelMatch;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function aggregate(values, method) {
|
|
130
|
+
if (!values.length) return null;
|
|
131
|
+
if (method === 'sum') return values.reduce((sum, value) => sum + value, 0);
|
|
132
|
+
if (method === 'minimum') return Math.min(...values);
|
|
133
|
+
if (method === 'maximum') return Math.max(...values);
|
|
134
|
+
if (method === 'latest') return values.at(-1);
|
|
135
|
+
return values.reduce((sum, value) => sum + value, 0) / values.length;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function evaluateGuardrail(guardrail, metric, definition) {
|
|
139
|
+
if (metric.state !== 'observed') return { ...guardrail, state: 'inconclusive', passed: null, reason: 'metric comparison is inconclusive' };
|
|
140
|
+
let passed;
|
|
141
|
+
if (guardrail.operator === 'gte') passed = metric.evaluation >= guardrail.threshold;
|
|
142
|
+
else if (guardrail.operator === 'lte') passed = metric.evaluation <= guardrail.threshold;
|
|
143
|
+
else if (guardrail.operator === 'increase_by') passed = metric.relative_change != null && metric.relative_change >= guardrail.threshold;
|
|
144
|
+
else if (guardrail.operator === 'decrease_by') passed = metric.relative_change != null && metric.relative_change <= -guardrail.threshold;
|
|
145
|
+
else if (definition.direction === 'increase') passed = metric.evaluation >= metric.baseline;
|
|
146
|
+
else if (definition.direction === 'decrease') passed = metric.evaluation <= metric.baseline;
|
|
147
|
+
else if (definition.direction === 'maintain') passed = metric.relative_change != null && Math.abs(metric.relative_change) <= guardrail.threshold;
|
|
148
|
+
else return { ...guardrail, state: 'inconclusive', passed: null, reason: 'contextual metric requires a user-defined directional guardrail' };
|
|
149
|
+
return { ...guardrail, state: 'observed', passed, reason: passed ? 'observed condition met' : 'observed condition not met' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function evaluateObjective(root, { objectiveId, refDate }) {
|
|
153
|
+
const { registries, problems } = loadRegistries(root);
|
|
154
|
+
if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
|
|
155
|
+
const objective = objectiveId ? registries.objectives.entries.find((item) => item.objective_id === objectiveId) : registries.objectives.entries[0];
|
|
156
|
+
if (!objective) throw new Error(objectiveId ? `objective not found: ${objectiveId}` : 'no objective configured');
|
|
157
|
+
const definitions = new Map(registries.metrics.entries.map((item) => [item.metric_id, item]));
|
|
158
|
+
const end = parseRefDate(refDate);
|
|
159
|
+
end.setUTCHours(23, 59, 59, 999);
|
|
160
|
+
const evaluationStart = new Date(end); evaluationStart.setUTCDate(evaluationStart.getUTCDate() - objective.comparison.evaluation_days + 1); evaluationStart.setUTCHours(0, 0, 0, 0);
|
|
161
|
+
const baselineEnd = new Date(evaluationStart.getTime() - 1);
|
|
162
|
+
const baselineStart = new Date(baselineEnd); baselineStart.setUTCDate(baselineStart.getUTCDate() - objective.comparison.baseline_days + 1); baselineStart.setUTCHours(0, 0, 0, 0);
|
|
163
|
+
const all = metricObservations(root).filter((item) => inCohort(item.data, objective.cohort));
|
|
164
|
+
const metricIds = [...new Set([...objective.primary_metrics, ...objective.supporting_metrics, ...objective.guardrails.map((item) => item.metric_id)])];
|
|
165
|
+
const metrics = metricIds.map((metricId) => {
|
|
166
|
+
const definition = definitions.get(metricId);
|
|
167
|
+
const observations = all.filter((item) => item.data.metric_id === metricId).sort((a, b) => a.data.observed_at.localeCompare(b.data.observed_at));
|
|
168
|
+
const baselineRows = observations.filter((item) => new Date(item.data.observed_at) >= baselineStart && new Date(item.data.observed_at) <= baselineEnd);
|
|
169
|
+
const evaluationRows = observations.filter((item) => new Date(item.data.observed_at) >= evaluationStart && new Date(item.data.observed_at) <= end);
|
|
170
|
+
const aggregatable = definition.aggregation !== 'provider_defined';
|
|
171
|
+
const enough = aggregatable && baselineRows.length >= objective.comparison.minimum_observations && evaluationRows.length >= objective.comparison.minimum_observations;
|
|
172
|
+
const baseline = enough ? aggregate(baselineRows.map((item) => item.data.value), definition.aggregation) : null;
|
|
173
|
+
const evaluation = enough ? aggregate(evaluationRows.map((item) => item.data.value), definition.aggregation) : null;
|
|
174
|
+
return {
|
|
175
|
+
metric_id: metricId,
|
|
176
|
+
provider: definition.provider,
|
|
177
|
+
state: enough ? 'observed' : 'inconclusive',
|
|
178
|
+
baseline,
|
|
179
|
+
evaluation,
|
|
180
|
+
absolute_change: enough ? evaluation - baseline : null,
|
|
181
|
+
relative_change: enough && baseline !== 0 ? (evaluation - baseline) / Math.abs(baseline) : null,
|
|
182
|
+
baseline_observations: baselineRows.length,
|
|
183
|
+
evaluation_observations: evaluationRows.length,
|
|
184
|
+
limitations: [
|
|
185
|
+
...definition.limitations,
|
|
186
|
+
...(!aggregatable ? ['provider_defined aggregation requires an explicit normalized aggregation before evaluation'] : []),
|
|
187
|
+
...(aggregatable && !enough ? [`requires at least ${objective.comparison.minimum_observations} observations in each window`] : []),
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
const guardrails = objective.guardrails.map((guardrail) => {
|
|
192
|
+
const metric = metrics.find((item) => item.metric_id === guardrail.metric_id);
|
|
193
|
+
return evaluateGuardrail(guardrail, metric, definitions.get(guardrail.metric_id));
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
objective_id: objective.objective_id,
|
|
197
|
+
objective_name: objective.name,
|
|
198
|
+
windows: { baseline_start: baselineStart.toISOString(), baseline_end: baselineEnd.toISOString(), evaluation_start: evaluationStart.toISOString(), evaluation_end: end.toISOString() },
|
|
199
|
+
metrics,
|
|
200
|
+
guardrails,
|
|
201
|
+
guardrail_status: guardrails.some((item) => item.state === 'inconclusive') ? 'inconclusive' : guardrails.some((item) => !item.passed) ? 'not_met' : 'met',
|
|
202
|
+
status: metrics.every((metric) => metric.state === 'observed') ? 'observed' : 'inconclusive',
|
|
203
|
+
interpretation: 'Changes are temporal associations. This evaluation does not establish that an intervention caused an observed outcome.',
|
|
204
|
+
};
|
|
205
|
+
}
|
package/src/installer/index.js
CHANGED
|
@@ -105,6 +105,8 @@ export function parseInstallerArgs(argv) {
|
|
|
105
105
|
help: false,
|
|
106
106
|
version: false,
|
|
107
107
|
failOnUpdate: false,
|
|
108
|
+
installMode: 'copy',
|
|
109
|
+
skillSelection: null,
|
|
108
110
|
_: [],
|
|
109
111
|
};
|
|
110
112
|
|
|
@@ -118,17 +120,29 @@ export function parseInstallerArgs(argv) {
|
|
|
118
120
|
else if (arg === '--dry-run') args.dryRun = true;
|
|
119
121
|
else if (arg === '--force') args.force = true;
|
|
120
122
|
else if (arg === '--json') args.json = true;
|
|
121
|
-
else if (arg === '--all') args.all = true;
|
|
123
|
+
else if (arg === '--all') { args.all = true; args.yes = true; }
|
|
122
124
|
else if (arg === '--help' || arg === '-h') args.help = true;
|
|
123
125
|
else if (arg === '--version' || arg === '-v') args.version = true;
|
|
124
|
-
else if (arg === '--project' || arg === '--local') args.scope = mergeScope(args.scope, 'project');
|
|
125
|
-
else if (arg === '--global' || arg === '--user') args.scope = mergeScope(args.scope, 'global');
|
|
126
|
+
else if (arg === '--project' || arg === '--local' || arg === '-p') args.scope = mergeScope(args.scope, 'project');
|
|
127
|
+
else if (arg === '--global' || arg === '--user' || arg === '-g') args.scope = mergeScope(args.scope, 'global');
|
|
128
|
+
else if (arg === '--copy') args.installMode = 'copy';
|
|
129
|
+
else if (arg === '--symlink') throw new CitableInstallerError('--symlink is not supported: Citable uses hashed managed copies for atomic validation and safe uninstall', EXIT_CODES.invalidArguments);
|
|
126
130
|
else if (arg === '--fail-on-update') args.failOnUpdate = true;
|
|
127
131
|
else if (arg === '--providers') {
|
|
128
132
|
if (i + 1 >= argv.length) throw new CitableInstallerError('--providers requires a value', EXIT_CODES.invalidArguments);
|
|
129
133
|
args.providersRaw = argv[++i];
|
|
130
134
|
} else if (arg.startsWith('--providers=')) {
|
|
131
135
|
args.providersRaw = arg.slice('--providers='.length);
|
|
136
|
+
} else if (arg === '--agent' || arg === '-a') {
|
|
137
|
+
if (i + 1 >= argv.length) throw new CitableInstallerError(`${arg} requires a value`, EXIT_CODES.invalidArguments);
|
|
138
|
+
args.providersRaw = [args.providersRaw, argv[++i]].filter(Boolean).join(',');
|
|
139
|
+
} else if (arg.startsWith('--agent=')) {
|
|
140
|
+
args.providersRaw = [args.providersRaw, arg.slice('--agent='.length)].filter(Boolean).join(',');
|
|
141
|
+
} else if (arg === '--skill' || arg === '-s') {
|
|
142
|
+
if (i + 1 >= argv.length) throw new CitableInstallerError(`${arg} requires a value`, EXIT_CODES.invalidArguments);
|
|
143
|
+
args.skillSelection = argv[++i];
|
|
144
|
+
} else if (arg.startsWith('--skill=')) {
|
|
145
|
+
args.skillSelection = arg.slice('--skill='.length);
|
|
132
146
|
} else if (arg === '--scope') {
|
|
133
147
|
if (i + 1 >= argv.length) throw new CitableInstallerError('--scope requires a value', EXIT_CODES.invalidArguments);
|
|
134
148
|
args.scope = mergeScope(args.scope, normalizeScopeValue(argv[++i]));
|
|
@@ -141,7 +155,16 @@ export function parseInstallerArgs(argv) {
|
|
|
141
155
|
}
|
|
142
156
|
}
|
|
143
157
|
|
|
144
|
-
if (args.
|
|
158
|
+
if (args.skillSelection !== null && !['citable', '*'].includes(String(args.skillSelection).trim().toLowerCase())) {
|
|
159
|
+
throw new CitableInstallerError(`this package contains only the citable skill; unsupported --skill value: ${args.skillSelection}`, EXIT_CODES.invalidArguments);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (args.all) {
|
|
163
|
+
if (args.providersRaw !== null) throw new CitableInstallerError('--all cannot be combined with --providers or --agent', EXIT_CODES.invalidArguments);
|
|
164
|
+
args.providerSelection = { kind: 'all', providers: PROVIDER_IDS, unknown: [] };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (args.providersRaw !== null && !args.providerSelection) {
|
|
145
168
|
const parsed = parseProviderList(args.providersRaw);
|
|
146
169
|
if (parsed.unknown.length) {
|
|
147
170
|
const supported = PROVIDER_IDS.join(', ');
|
|
@@ -1102,12 +1125,17 @@ Install examples
|
|
|
1102
1125
|
npx @nebulacomponents/citable install
|
|
1103
1126
|
npx @nebulacomponents/citable install --yes
|
|
1104
1127
|
npx @nebulacomponents/citable install --providers=claude,codex,cursor --project --yes
|
|
1128
|
+
npx @nebulacomponents/citable install --agent claude-code --agent codex --skill citable --copy -p -y
|
|
1105
1129
|
npx @nebulacomponents/citable install --providers=all --global --dry-run
|
|
1106
1130
|
|
|
1107
1131
|
Options
|
|
1108
|
-
--providers=<list> Provider ids, detected, or
|
|
1109
|
-
--
|
|
1110
|
-
--
|
|
1132
|
+
--providers=<list> Provider ids, detected, all, or *
|
|
1133
|
+
--agent=<id>, -a <id> Repeatable provider alias compatible with skills CLI
|
|
1134
|
+
--skill=<name>, -s <name> Select citable or * (this package contains one skill)
|
|
1135
|
+
--all Select every provider and confirm non-interactively
|
|
1136
|
+
--copy Use managed copies (default and only install mode)
|
|
1137
|
+
--project, --local, -p Install/check project-local skill locations
|
|
1138
|
+
--global, --user, -g Install/check user-global skill locations
|
|
1111
1139
|
--scope=<project|global> Scope equivalent
|
|
1112
1140
|
--yes, -y Confirm non-interactively
|
|
1113
1141
|
--dry-run Preview filesystem changes without mutation
|
|
@@ -130,7 +130,7 @@ export function parseProviderList(value) {
|
|
|
130
130
|
if (!raw) return { kind: 'explicit', providers: [], unknown: [] };
|
|
131
131
|
const lowered = raw.toLowerCase();
|
|
132
132
|
if (lowered === 'detected') return { kind: 'detected', providers: [], unknown: [] };
|
|
133
|
-
if (lowered === 'all') return { kind: 'all', providers: PROVIDER_IDS, unknown: [] };
|
|
133
|
+
if (lowered === 'all' || lowered === '*') return { kind: 'all', providers: PROVIDER_IDS, unknown: [] };
|
|
134
134
|
|
|
135
135
|
const providers = [];
|
|
136
136
|
const unknown = [];
|