@nebulacomponents/citable 1.9.0 → 1.10.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.
- package/CHANGELOG.md +10 -0
- package/README.md +4 -2
- package/dist/universal/.agents/skills/citable/SKILL.md +5 -2
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/commands/README.md +1 -0
- package/dist/universal/.agents/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.agents/skills/citable/manifest.json +8 -6
- package/dist/universal/.agents/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.claude/skills/citable/SKILL.md +5 -2
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/commands/README.md +1 -0
- package/dist/universal/.claude/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.claude/skills/citable/manifest.json +8 -6
- package/dist/universal/.claude/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +5 -2
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/commands/README.md +1 -0
- package/dist/universal/.cursor/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.cursor/skills/citable/manifest.json +8 -6
- package/dist/universal/.cursor/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +5 -2
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/commands/README.md +1 -0
- package/dist/universal/.gemini/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.gemini/skills/citable/manifest.json +8 -6
- package/dist/universal/.gemini/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.github/skills/citable/SKILL.md +5 -2
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/commands/README.md +1 -0
- package/dist/universal/.github/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.github/skills/citable/manifest.json +8 -6
- package/dist/universal/.github/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +5 -2
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/commands/README.md +1 -0
- package/dist/universal/.kiro/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.kiro/skills/citable/manifest.json +8 -6
- package/dist/universal/.kiro/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +5 -2
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/commands/README.md +1 -0
- package/dist/universal/.opencode/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.opencode/skills/citable/manifest.json +8 -6
- package/dist/universal/.opencode/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +5 -2
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/commands/README.md +1 -0
- package/dist/universal/.pi/agent/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.pi/agent/skills/citable/manifest.json +8 -6
- package/dist/universal/.pi/agent/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +5 -2
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/commands/README.md +1 -0
- package/dist/universal/.qoder/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.qoder/skills/citable/manifest.json +8 -6
- package/dist/universal/.qoder/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +5 -2
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/commands/README.md +1 -0
- package/dist/universal/.rovodev/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.rovodev/skills/citable/manifest.json +8 -6
- package/dist/universal/.rovodev/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +5 -2
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/commands/README.md +1 -0
- package/dist/universal/.trae/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.trae/skills/citable/manifest.json +8 -6
- package/dist/universal/.trae/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +5 -2
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/commands/README.md +1 -0
- package/dist/universal/.trae-cn/skills/citable/commands/delivery.md +18 -0
- package/dist/universal/.trae-cn/skills/citable/manifest.json +8 -6
- package/dist/universal/.trae-cn/skills/citable/schemas/schedule.schema.json +4 -0
- package/dist/universal/manifest.json +99 -75
- package/package.json +1 -1
- package/schemas/schedule.schema.json +4 -0
- package/skill/SKILL.md +5 -2
- package/skill/commands/README.md +1 -0
- package/skill/commands/delivery.md +18 -0
- package/src/cli/index.js +15 -0
- package/src/commands/compareSnapshots.js +15 -0
- package/src/commands/delivery.js +50 -0
- package/src/registries/index.js +1 -0
package/src/cli/index.js
CHANGED
|
@@ -19,6 +19,7 @@ import { evaluateObjective, importMetrics, initializeObjective, validateObjectiv
|
|
|
19
19
|
import { configureConnection, connectionStatus, discoverConnections, disconnectConnection, syncConnection, validateConnection } from '../commands/connect.js';
|
|
20
20
|
import { evaluateDispositions, validateGovernance } from '../commands/governance.js';
|
|
21
21
|
import { evaluateReviews, initializeSamplingPlan, prioritizeReviews, queueReviews, selectSample } from '../commands/reviews.js';
|
|
22
|
+
import { projectGithub, runSchedule } from '../commands/delivery.js';
|
|
22
23
|
|
|
23
24
|
const HELP = `citable — SEO / AEO / GEO audit, remediation, validation, and governance
|
|
24
25
|
|
|
@@ -62,6 +63,8 @@ Commands
|
|
|
62
63
|
reviews plan Validate/add a sampling plan from --input
|
|
63
64
|
reviews sample [plan] Select a reproducible census or seeded random sample
|
|
64
65
|
reviews evaluate Detect stale decisions and require disagreement adjudication
|
|
66
|
+
schedules run [id] Execute an active version-pinned audit schedule
|
|
67
|
+
project github [run] Render non-authoritative GitHub annotations from a run
|
|
65
68
|
self-upgrade Check for a newer version and upgrade the npx cache
|
|
66
69
|
|
|
67
70
|
Options
|
|
@@ -272,6 +275,18 @@ export async function main(argv = process.argv.slice(2), options = {}) {
|
|
|
272
275
|
out(args,`reviews ${mode}: ${r.created?.length ?? r.items?.length ?? r.selected_item_ids?.length ?? r.results?.length ?? 1} item(s)${args.write?' written':' (dry run)'}`,r);
|
|
273
276
|
break;
|
|
274
277
|
}
|
|
278
|
+
case 'schedules': {
|
|
279
|
+
if(args._[0]!=='run') throw new Error('usage: citable schedules run <schedule-id> [--ref-date YYYY-MM-DD]');
|
|
280
|
+
const r=await runSchedule(root,{scheduleId:args._[1],refDate:args.refDate});
|
|
281
|
+
out(args,`schedule ${r.schedule_execution.schedule_id}: audit ${r.runId}\nEvidence package: ${r.dir}\nExecution record: ${r.execution_file}`,r);
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case 'project': {
|
|
285
|
+
if(args._[0]!=='github') throw new Error('usage: citable project github <run-id>');
|
|
286
|
+
const r=projectGithub(root,{runId:args._[1]});
|
|
287
|
+
out(args,`project github ${r.source_run_id}: ${r.annotations.length} annotation(s)\nProjection: ${path.join(r.dir,'annotations.json')}`,r);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
275
290
|
case 'self-upgrade': {
|
|
276
291
|
const output = await selfUpgradeCommand(argv.slice(1));
|
|
277
292
|
console.log(output);
|
|
@@ -27,6 +27,16 @@ export function compareSnapshots(root, { runA, runB } = {}) {
|
|
|
27
27
|
};
|
|
28
28
|
const a = load(runA);
|
|
29
29
|
const b = load(runB);
|
|
30
|
+
const changedKeys = (left = {}, right = {}) => [...new Set([...Object.keys(left), ...Object.keys(right)])].filter((key) => left[key] !== right[key]);
|
|
31
|
+
const changeDimensions = {
|
|
32
|
+
resource_changed: changedKeys(a.manifest.input_hashes, b.manifest.input_hashes),
|
|
33
|
+
evidence_artifacts_changed: changedKeys(a.manifest.output_hashes, b.manifest.output_hashes),
|
|
34
|
+
detector_set_changed: JSON.stringify(a.manifest.detectors_run) !== JSON.stringify(b.manifest.detectors_run),
|
|
35
|
+
configuration_changed: a.manifest.configuration_hash !== b.manifest.configuration_hash,
|
|
36
|
+
observation_method_changed: a.manifest.command !== b.manifest.command || JSON.stringify(a.manifest.argv) !== JSON.stringify(b.manifest.argv) || a.manifest.target?.kind !== b.manifest.target?.kind,
|
|
37
|
+
tool_changed: a.manifest.tool_version !== b.manifest.tool_version,
|
|
38
|
+
external_system_may_have_changed: a.manifest.target?.kind === 'url' || b.manifest.target?.kind === 'url',
|
|
39
|
+
};
|
|
30
40
|
const key = (f) => `${f.detector_id}|${f.subject.identifier}|${f.observation.summary}`;
|
|
31
41
|
const aKeys = new Map(a.findings.map((f) => [key(f), f]));
|
|
32
42
|
const bKeys = new Map(b.findings.map((f) => [key(f), f]));
|
|
@@ -38,6 +48,11 @@ export function compareSnapshots(root, { runA, runB } = {}) {
|
|
|
38
48
|
baseline_timestamp: a.manifest.timestamp,
|
|
39
49
|
comparison_timestamp: b.manifest.timestamp,
|
|
40
50
|
regressions, resolved, persisting,
|
|
51
|
+
comparability: {
|
|
52
|
+
comparable: !changeDimensions.detector_set_changed && !changeDimensions.configuration_changed && !changeDimensions.observation_method_changed && !changeDimensions.tool_changed,
|
|
53
|
+
change_dimensions: changeDimensions,
|
|
54
|
+
limitation: 'These dimensions identify observed differences between run envelopes; they do not establish what caused a finding change.',
|
|
55
|
+
},
|
|
41
56
|
summary: {
|
|
42
57
|
new_findings: regressions.length,
|
|
43
58
|
resolved_findings: resolved.length,
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { audit } from './audit.js';
|
|
5
|
+
import { loadRegistries } from '../registries/index.js';
|
|
6
|
+
import { readJson, sha256, writeJson, nowIso } from '../shared/io.js';
|
|
7
|
+
|
|
8
|
+
const PKG = readJson(path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../package.json'));
|
|
9
|
+
|
|
10
|
+
export async function runSchedule(root, { scheduleId, refDate } = {}) {
|
|
11
|
+
const { registries, problems } = loadRegistries(root);
|
|
12
|
+
if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
|
|
13
|
+
const schedule = registries.schedules.entries.find((item) => item.schedule_id === scheduleId);
|
|
14
|
+
if (!schedule) throw new Error(`schedule not found: ${scheduleId}`);
|
|
15
|
+
if (schedule.status !== 'active') throw new Error(`schedule ${scheduleId} is ${schedule.status}`);
|
|
16
|
+
if (schedule.expected_tool_version !== PKG.version) throw new Error(`schedule ${scheduleId} expects Citable ${schedule.expected_tool_version}, current version is ${PKG.version}`);
|
|
17
|
+
const result = await audit(root, { target: schedule.audit.target, scope: schedule.audit.scope, baseUrl: schedule.audit.base_url, refDate });
|
|
18
|
+
const execution = { schedule_id: scheduleId, cron: schedule.cron, owner: schedule.owner, executed_at: nowIso(), run_id: result.runId, manifest_hash: sha256(fs.readFileSync(path.join(result.dir, 'manifest.json'))), limitations: schedule.limitations };
|
|
19
|
+
const executionFile = path.join(root, '.citable', 'schedule-executions', `${result.runId}.json`);
|
|
20
|
+
writeJson(executionFile, execution);
|
|
21
|
+
return { ...result, schedule_execution: execution, execution_file: executionFile };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function annotationLevel(severity) {
|
|
25
|
+
if (['critical', 'high'].includes(severity)) return 'failure';
|
|
26
|
+
if (severity === 'medium') return 'warning';
|
|
27
|
+
return 'notice';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function projectGithub(root, { runId } = {}) {
|
|
31
|
+
if (!runId) throw new Error('projection requires a source run id');
|
|
32
|
+
const runDir = path.join(root, '.citable', 'runs', runId);
|
|
33
|
+
const findingsFile = path.join(runDir, 'findings.json');
|
|
34
|
+
const manifestFile = path.join(runDir, 'manifest.json');
|
|
35
|
+
if (!fs.existsSync(findingsFile) || !fs.existsSync(manifestFile)) throw new Error(`run ${runId} is missing findings or manifest evidence`);
|
|
36
|
+
const findingsBytes = fs.readFileSync(findingsFile);
|
|
37
|
+
const manifestBytes = fs.readFileSync(manifestFile);
|
|
38
|
+
const annotations = JSON.parse(findingsBytes).map((finding) => ({
|
|
39
|
+
level: annotationLevel(finding.classification.severity),
|
|
40
|
+
title: `${finding.detector_id}: ${finding.classification.severity}`,
|
|
41
|
+
message: finding.observation.summary,
|
|
42
|
+
path: finding.subject.source_file || null,
|
|
43
|
+
line: finding.subject.source_location ? Number(String(finding.subject.source_location).match(/\d+/)?.[0]) || null : null,
|
|
44
|
+
finding_id: finding.finding_id,
|
|
45
|
+
}));
|
|
46
|
+
const projection = { source_run_id: runId, source_manifest_hash: sha256(manifestBytes), source_findings_hash: sha256(findingsBytes), generated_at: nowIso(), projection: 'github-check-annotations', authoritative: false, annotations };
|
|
47
|
+
const dir = path.join(root, '.citable', 'projections', 'github', runId);
|
|
48
|
+
writeJson(path.join(dir, 'annotations.json'), projection);
|
|
49
|
+
return { ...projection, dir };
|
|
50
|
+
}
|
package/src/registries/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export const REGISTRY_SPECS = [
|
|
|
22
22
|
{ file: 'exceptions.yaml', kind: 'exceptions', schema: 'exception.schema.json', idField: 'exception_id' },
|
|
23
23
|
{ file: 'review-items.yaml', kind: 'review_items', schema: 'review-item.schema.json', idField: 'review_item_id' },
|
|
24
24
|
{ file: 'sampling-plans.yaml', kind: 'sampling_plans', schema: 'sampling-plan.schema.json', idField: 'sampling_plan_id' },
|
|
25
|
+
{ file: 'schedules.yaml', kind: 'schedules', schema: 'schedule.schema.json', idField: 'schedule_id' },
|
|
25
26
|
];
|
|
26
27
|
|
|
27
28
|
export function contextDir(root) {
|