codehere 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -192
- package/dist/application/use-cases/check-claimed-tests.js +81 -0
- package/dist/application/use-cases/detect-test-weakening.js +237 -0
- package/dist/application/use-cases/rerun-tests.js +110 -7
- package/dist/index.js +38 -1212
- package/dist/infrastructure/orchestration/artifacts-extractor.js +2 -323
- package/dist/presentation/cli/commands/check-command.js +178 -23
- package/dist/presentation/cli/commands/help-command.js +21 -285
- package/dist/ui.js +5 -236
- package/dist/utils/version.js +0 -6
- package/package.json +9 -38
- package/dist/application/services/verdict-service.js +0 -154
- package/dist/application/use-cases/assess-cross-vendor.js +0 -126
- package/dist/application/use-cases/build-cross-vendor-verdict.js +0 -118
- package/dist/application/use-cases/domain-trust.js +0 -110
- package/dist/application/use-cases/export-aat.js +0 -107
- package/dist/application/use-cases/generate-acceptance-criteria.js +0 -223
- package/dist/application/use-cases/generate-final-report.js +0 -259
- package/dist/application/use-cases/generate-narration.js +0 -130
- package/dist/application/use-cases/generate-thread-title.js +0 -181
- package/dist/application/use-cases/narrator-prompt-template.js +0 -158
- package/dist/application/use-cases/plan-auto-review.js +0 -57
- package/dist/application/use-cases/reconcile-audit.js +0 -100
- package/dist/application/use-cases/search-sessions.js +0 -70
- package/dist/application/use-cases/should-auto-review.js +0 -71
- package/dist/application/use-cases/thread-export.js +0 -245
- package/dist/application/use-cases/verify-claim.js +0 -456
- package/dist/application/use-cases/write-pass.js +0 -277
- package/dist/domain/audit.js +0 -11
- package/dist/domain/services/codebase-detector.js +0 -85
- package/dist/domain/task.js +0 -91
- package/dist/error-handler.js +0 -139
- package/dist/infrastructure/completion/bash-completion.js +0 -29
- package/dist/infrastructure/completion/fish-completion.js +0 -17
- package/dist/infrastructure/completion/zsh-completion.js +0 -25
- package/dist/infrastructure/config/config-manager.js +0 -174
- package/dist/infrastructure/config/product-context.js +0 -49
- package/dist/infrastructure/context/adapters/drawio-source.js +0 -157
- package/dist/infrastructure/context/adapters/figma-source.js +0 -198
- package/dist/infrastructure/context/adapters/github-issue-source.js +0 -160
- package/dist/infrastructure/context/adapters/linear-source.js +0 -190
- package/dist/infrastructure/context/context-resolver.js +0 -144
- package/dist/infrastructure/context/context-source.js +0 -114
- package/dist/infrastructure/context/github-api.js +0 -110
- package/dist/infrastructure/context/github-device-flow.js +0 -65
- package/dist/infrastructure/context/oauth-flow.js +0 -207
- package/dist/infrastructure/context/oauth-store.js +0 -122
- package/dist/infrastructure/context/registry-bootstrap.js +0 -95
- package/dist/infrastructure/context/write-pass-resolver.js +0 -106
- package/dist/infrastructure/cost/budget-config.js +0 -114
- package/dist/infrastructure/cost/cost-router.js +0 -97
- package/dist/infrastructure/cost/spend-aggregator.js +0 -222
- package/dist/infrastructure/cost/team-ledger.js +0 -355
- package/dist/infrastructure/discovery/discovery-log.js +0 -85
- package/dist/infrastructure/hooks/audit-tip-signature.js +0 -336
- package/dist/infrastructure/hooks/claude-sdk-hook.js +0 -1018
- package/dist/infrastructure/hooks/hook-mode.js +0 -87
- package/dist/infrastructure/hooks/mirror-tool-call-to-audit.js +0 -49
- package/dist/infrastructure/hooks/pre-prompt-scan.js +0 -122
- package/dist/infrastructure/hooks/session-analyzer.js +0 -177
- package/dist/infrastructure/observability/execution-tracer.js +0 -227
- package/dist/infrastructure/orchestration/agent-adapter.js +0 -13
- package/dist/infrastructure/orchestration/agent-installer.js +0 -101
- package/dist/infrastructure/orchestration/agents-config-seed.js +0 -68
- package/dist/infrastructure/orchestration/aider-adapter.js +0 -611
- package/dist/infrastructure/orchestration/cassette-recorder.js +0 -120
- package/dist/infrastructure/orchestration/claude-code-adapter.js +0 -592
- package/dist/infrastructure/orchestration/codex-adapter.js +0 -574
- package/dist/infrastructure/orchestration/conductor-adapter.js +0 -416
- package/dist/infrastructure/orchestration/context-handoff.js +0 -162
- package/dist/infrastructure/orchestration/control/codex-control-surface.js +0 -99
- package/dist/infrastructure/orchestration/control/control-surface.js +0 -15
- package/dist/infrastructure/orchestration/detect-ollama.js +0 -75
- package/dist/infrastructure/orchestration/failover-classifier.js +0 -151
- package/dist/infrastructure/orchestration/failover-config.js +0 -67
- package/dist/infrastructure/orchestration/failover-policy.js +0 -106
- package/dist/infrastructure/orchestration/gemini-adapter.js +0 -663
- package/dist/infrastructure/orchestration/generic-cli-adapter.js +0 -689
- package/dist/infrastructure/orchestration/graphify-context.js +0 -209
- package/dist/infrastructure/orchestration/memory-injector.js +0 -380
- package/dist/infrastructure/orchestration/mocks/mock-agent-repl.js +0 -388
- package/dist/infrastructure/orchestration/mocks/mock-codex-app-server.js +0 -73
- package/dist/infrastructure/orchestration/mocks/mock-synthesis.js +0 -83
- package/dist/infrastructure/orchestration/mocks/resolve-agent-spawn.js +0 -37
- package/dist/infrastructure/orchestration/opencode-adapter.js +0 -580
- package/dist/infrastructure/orchestration/progress-aggregator.js +0 -906
- package/dist/infrastructure/orchestration/readiness.js +0 -103
- package/dist/infrastructure/orchestration/session-manager.js +0 -1590
- package/dist/infrastructure/orchestration/session-persistence.js +0 -394
- package/dist/infrastructure/orchestration/thread-key-store.js +0 -127
- package/dist/infrastructure/orchestration/write-pass-store.js +0 -38
- package/dist/infrastructure/protocols/cloud-sync.js +0 -207
- package/dist/infrastructure/protocols/mcp-helpers.js +0 -25
- package/dist/infrastructure/protocols/mcp-server-impl.js +0 -3935
- package/dist/infrastructure/protocols/mcp-tools.js +0 -401
- package/dist/infrastructure/protocols/probe-port.js +0 -36
- package/dist/infrastructure/protocols/thread-phase.js +0 -105
- package/dist/infrastructure/protocols/ui/app-script-artifacts.js +0 -121
- package/dist/infrastructure/protocols/ui/app-script-audit-footer.js +0 -44
- package/dist/infrastructure/protocols/ui/app-script-audit-verify.js +0 -56
- package/dist/infrastructure/protocols/ui/app-script-composer-cost.js +0 -588
- package/dist/infrastructure/protocols/ui/app-script-context-sources.js +0 -168
- package/dist/infrastructure/protocols/ui/app-script-conversation.js +0 -67
- package/dist/infrastructure/protocols/ui/app-script-drawer.js +0 -115
- package/dist/infrastructure/protocols/ui/app-script-failover.js +0 -133
- package/dist/infrastructure/protocols/ui/app-script-github.js +0 -113
- package/dist/infrastructure/protocols/ui/app-script-observer.js +0 -104
- package/dist/infrastructure/protocols/ui/app-script-onboarding.js +0 -80
- package/dist/infrastructure/protocols/ui/app-script-overlay.js +0 -203
- package/dist/infrastructure/protocols/ui/app-script-recovery.js +0 -136
- package/dist/infrastructure/protocols/ui/app-script-render.js +0 -166
- package/dist/infrastructure/protocols/ui/app-script-share.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-sidebar.js +0 -194
- package/dist/infrastructure/protocols/ui/app-script-tour.js +0 -78
- package/dist/infrastructure/protocols/ui/app-script-wpass.js +0 -312
- package/dist/infrastructure/protocols/ui/app-script.js +0 -2998
- package/dist/infrastructure/protocols/ui/font-faces.js +0 -7
- package/dist/infrastructure/protocols/ui/styles.js +0 -2147
- package/dist/infrastructure/protocols/ui/tokens.js +0 -96
- package/dist/infrastructure/protocols/ui-page-v2.js +0 -320
- package/dist/infrastructure/protocols/verify-page.js +0 -1677
- package/dist/infrastructure/security/file-collection.js +0 -118
- package/dist/infrastructure/security/fix-code.js +0 -144
- package/dist/infrastructure/security/fix-secret.js +0 -89
- package/dist/infrastructure/security/permissions-config.js +0 -235
- package/dist/infrastructure/security/scan-types.js +0 -10
- package/dist/infrastructure/security/ship-scan.js +0 -195
- package/dist/infrastructure/security/trust-scorer.js +0 -724
- package/dist/infrastructure/security/vibe-checks.js +0 -99
- package/dist/infrastructure/storage/atomic-write.js +0 -86
- package/dist/infrastructure/storage/cross-tool-agents.js +0 -268
- package/dist/infrastructure/storage/project-thread-migration.js +0 -237
- package/dist/infrastructure/storage/project-thread-mirror.js +0 -671
- package/dist/infrastructure/storage/project-thread-reader.js +0 -409
- package/dist/infrastructure/storage/project-thread-title-backfill.js +0 -214
- package/dist/monitoring.js +0 -212
- package/dist/presentation/cli/commands/audit-command.js +0 -201
- package/dist/presentation/cli/commands/budget-command.js +0 -79
- package/dist/presentation/cli/commands/config-command.js +0 -125
- package/dist/presentation/cli/commands/context-source-command.js +0 -217
- package/dist/presentation/cli/commands/hook-command.js +0 -265
- package/dist/presentation/cli/commands/init-command.js +0 -590
- package/dist/presentation/cli/commands/memory-command.js +0 -1031
- package/dist/presentation/cli/commands/narrator-command.js +0 -101
- package/dist/presentation/cli/commands/permission-command.js +0 -105
- package/dist/presentation/cli/commands/protocols-builder.js +0 -254
- package/dist/presentation/cli/commands/protocols-command.js +0 -84
- package/dist/presentation/cli/commands/repo-map-builder.js +0 -247
- package/dist/presentation/cli/commands/report-command.js +0 -403
- package/dist/presentation/cli/commands/scan-command.js +0 -52
- package/dist/presentation/cli/commands/scan-tool-command.js +0 -110
- package/dist/presentation/cli/commands/search-command.js +0 -51
- package/dist/presentation/cli/commands/setup-command.js +0 -330
- package/dist/presentation/cli/commands/status-command.js +0 -614
- package/dist/presentation/cli/commands/team-command.js +0 -256
- package/dist/presentation/cli/commands/thread-command.js +0 -370
- package/dist/presentation/cli/commands/trust-command.js +0 -328
- package/dist/presentation/cli/commands/verify-command.js +0 -298
- package/dist/presentation/cli/error-display.js +0 -328
- package/dist/presentation/cli/repl.js +0 -805
- package/dist/presentation/cli/task-deliverable.js +0 -153
- package/dist/testing/cassette.js +0 -125
- package/dist/testing/replay-adapter.js +0 -147
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codehere",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Re-runs the tests an AI says passed, and tells you whether they did. Flags weakened tests and test files that do not exist.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -25,8 +25,6 @@
|
|
|
25
25
|
"prebuild": "rm -rf dist",
|
|
26
26
|
"build": "tsc",
|
|
27
27
|
"postbuild": "chmod +x dist/index.js",
|
|
28
|
-
"repomap": "node scripts/gen-repo-map.mjs",
|
|
29
|
-
"pattern-counts": "node scripts/print-pattern-counts.mjs",
|
|
30
28
|
"prepublishOnly": "npm run build && find dist -name '*.map' -delete && find dist -name '*.d.ts' -delete && find dist -name '*.d.ts.map' -delete",
|
|
31
29
|
"start": "node dist/index.js",
|
|
32
30
|
"dev": "tsx src/index.ts",
|
|
@@ -35,34 +33,19 @@
|
|
|
35
33
|
"lint": "eslint src/ --ext .ts",
|
|
36
34
|
"knip": "knip",
|
|
37
35
|
"knip:gate": "node scripts/knip-gate.mjs",
|
|
38
|
-
"
|
|
39
|
-
"arch:gate": "tsc --noEmit && eslint src/ --ext .ts && node scripts/knip-gate.mjs && node scripts/ui-token-gate.mjs",
|
|
36
|
+
"arch:gate": "tsc --noEmit && eslint src/ --ext .ts && node scripts/knip-gate.mjs",
|
|
40
37
|
"format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
|
|
41
38
|
"format:check": "prettier --check 'src/**/*.ts' 'test/**/*.ts'",
|
|
42
39
|
"sbom": "npm sbom --sbom-format=spdx --omit=dev",
|
|
43
|
-
"pretag:check": "../scripts/pretag-npm-check.sh"
|
|
44
|
-
"cassette:record": "../scripts/capture-cassette.sh",
|
|
45
|
-
"e2e": "node test/e2e/run.mjs",
|
|
46
|
-
"e2e:live": "CODEHERE_E2E_LIVE=1 node test/e2e/run.mjs",
|
|
47
|
-
"degraded": "node scripts/degraded-matrix.mjs",
|
|
48
|
-
"inventory": "node scripts/inventory-drift.mjs"
|
|
40
|
+
"pretag:check": "../scripts/pretag-npm-check.sh"
|
|
49
41
|
},
|
|
50
42
|
"keywords": [
|
|
51
43
|
"ai-coding-agents",
|
|
52
|
-
"verification",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"ai-cost-tracking",
|
|
56
|
-
"audit-trail",
|
|
57
|
-
"trust-scoring",
|
|
58
|
-
"ai-safety",
|
|
59
|
-
"ai-governance",
|
|
44
|
+
"test-verification",
|
|
45
|
+
"ci",
|
|
46
|
+
"github-action",
|
|
60
47
|
"claude-code",
|
|
61
48
|
"codex",
|
|
62
|
-
"aider",
|
|
63
|
-
"opencode",
|
|
64
|
-
"hook-system",
|
|
65
|
-
"sast",
|
|
66
49
|
"cli-tool",
|
|
67
50
|
"typescript"
|
|
68
51
|
],
|
|
@@ -75,13 +58,10 @@
|
|
|
75
58
|
"bugs": {
|
|
76
59
|
"url": "https://github.com/muhammadegaa/codehere/issues"
|
|
77
60
|
},
|
|
78
|
-
"homepage": "https://codehere
|
|
61
|
+
"homepage": "https://github.com/muhammadegaa/codehere",
|
|
79
62
|
"dependencies": {
|
|
80
63
|
"chalk": "^5.3.0",
|
|
81
|
-
"
|
|
82
|
-
"commander": "^11.1.0",
|
|
83
|
-
"dotenv": "^16.3.1",
|
|
84
|
-
"yaml": "^2.8.4"
|
|
64
|
+
"commander": "^11.1.0"
|
|
85
65
|
},
|
|
86
66
|
"devDependencies": {
|
|
87
67
|
"@eslint/js": "^10.0.1",
|
|
@@ -90,19 +70,10 @@
|
|
|
90
70
|
"eslint-import-resolver-typescript": "^4.4.5",
|
|
91
71
|
"eslint-plugin-import-x": "^4.16.2",
|
|
92
72
|
"knip": "^6.16.1",
|
|
93
|
-
"playwright": "^1.60.0",
|
|
94
73
|
"prettier": "^3.8.3",
|
|
95
74
|
"tsx": "^4.21.0",
|
|
96
75
|
"typescript": "^5.9.3",
|
|
97
76
|
"typescript-eslint": "^8.58.2",
|
|
98
77
|
"vitest": "^4.1.4"
|
|
99
|
-
},
|
|
100
|
-
"peerDependencies": {
|
|
101
|
-
"@mohammednagy/graphify-ts": "^0.22.3"
|
|
102
|
-
},
|
|
103
|
-
"peerDependenciesMeta": {
|
|
104
|
-
"@mohammednagy/graphify-ts": {
|
|
105
|
-
"optional": true
|
|
106
|
-
}
|
|
107
78
|
}
|
|
108
79
|
}
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VerdictService — the single owner of how a session's honesty verdict is
|
|
3
|
-
* ASSESSED from its parts (R11 / ARCHITECTURE.md M2).
|
|
4
|
-
*
|
|
5
|
-
* Before this, every surface re-derived the verdict inputs inline: the HTTP
|
|
6
|
-
* `/api/tasks` route did it twice (spawned + persisted records) and the
|
|
7
|
-
* `codehere verify` CLI did it a third time, each independently mapping a session
|
|
8
|
-
* to `verifyClaim({ summary, artifacts, steps })`. A change to what feeds the
|
|
9
|
-
* verdict meant editing 3+ sites in 2 layers — the "verdict has no owner" finding.
|
|
10
|
-
*
|
|
11
|
-
* This is the one entry point. It COMPOSES the pure use-cases (today `verifyClaim`;
|
|
12
|
-
* `reconcileAudit`, composed into the single `SessionVerdict`) — it does
|
|
13
|
-
* NOT re-implement their logic. Surfaces render what this returns; they never
|
|
14
|
-
* recompute a verdict rule.
|
|
15
|
-
*/
|
|
16
|
-
import { verifyClaim } from '../use-cases/verify-claim.js';
|
|
17
|
-
/**
|
|
18
|
-
* Assess a session's claim-vs-reality verdict. The single owner of the
|
|
19
|
-
* session → `verifyClaim` input mapping. Pure (delegates to the pure use-case).
|
|
20
|
-
*/
|
|
21
|
-
export function assessSession(session) {
|
|
22
|
-
return verifyClaim({
|
|
23
|
-
summary: session.finalReport?.summary,
|
|
24
|
-
artifacts: session.artifacts,
|
|
25
|
-
steps: session.steps,
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
/** Compose the single verdict object every surface consumes. */
|
|
29
|
-
export function composeVerdict(session, audit, crossVendor) {
|
|
30
|
-
const verdict = {
|
|
31
|
-
claim: assessSession(session),
|
|
32
|
-
...(audit !== undefined ? { audit } : {}),
|
|
33
|
-
...(crossVendor !== undefined ? { crossVendor } : {}),
|
|
34
|
-
};
|
|
35
|
-
// The one owner attaches the grade so no surface re-derives "is this green".
|
|
36
|
-
verdict.grade = gradeComposite(verdict);
|
|
37
|
-
return verdict;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* The one canonical verdict vocabulary. Every surface (web app + CLI) reads its
|
|
41
|
-
* verdict word from here, so the same state reads the same word everywhere
|
|
42
|
-
* instead of each surface re-deriving its own ("Proven" vs "Verified" vs
|
|
43
|
-
* "verified" for the flagship cross-vendor case was a real three-way split).
|
|
44
|
-
* Wording locked with Ega 2026-06-23, refined 2026-07-17: a green composite grade
|
|
45
|
-
* reads "Verified" on EVERY surface — a single other-vendor review CORROBORATES,
|
|
46
|
-
* it doesn't prove, so the seal expands it as "Corroborated by <vendor>" and no
|
|
47
|
-
* headline says "Proven" for a lone reviewer. "Proven" stays in the vocab
|
|
48
|
-
* (reserved) but is not rendered today. "Caught" = a contradicted claim.
|
|
49
|
-
*
|
|
50
|
-
* The first seven are the canonical headline words. `partial` is a residual the
|
|
51
|
-
* web still emits for two narrow "couldn't fully verify" branches (not-a-git-repo
|
|
52
|
-
* and the catch-all); it is kept here so nothing is silently dropped, pending a
|
|
53
|
-
* decision to fold it into `unverified`.
|
|
54
|
-
*/
|
|
55
|
-
export const VERDICT_WORDS = {
|
|
56
|
-
proven: 'Proven',
|
|
57
|
-
verified: 'Verified',
|
|
58
|
-
reported: 'Reported',
|
|
59
|
-
stopped: 'Stopped',
|
|
60
|
-
caught: 'Caught',
|
|
61
|
-
unverified: 'Unverified',
|
|
62
|
-
practice: 'Practice',
|
|
63
|
-
partial: 'Partial',
|
|
64
|
-
};
|
|
65
|
-
/**
|
|
66
|
-
* The green-verdict corroboration clause, scoped to the ACTUAL independent
|
|
67
|
-
* signal(s) that earned the green. A LONE cross-vendor pass is one reviewer's
|
|
68
|
-
* corroboration — "corroborated by one other vendor" — never "independently
|
|
69
|
-
* corroborated", which would overstate a single reviewer. Disk-observed writes
|
|
70
|
-
* and multi-signal greens keep the "independently corroborated" wording. The
|
|
71
|
-
* web mirrors this inline (app-script `indepClause`) so both surfaces name the
|
|
72
|
-
* same signal for the same state.
|
|
73
|
-
*/
|
|
74
|
-
export function corroborationClause(grade) {
|
|
75
|
-
const pass = grade.signals.filter((s) => s.provenance !== 'self-reported' && s.state === 'pass');
|
|
76
|
-
if (pass.length === 1 && pass[0].provenance === 'adversarial')
|
|
77
|
-
return 'corroborated by one other vendor.';
|
|
78
|
-
const hasDisk = pass.some((s) => s.provenance === 'observed');
|
|
79
|
-
return hasDisk ? 'independently corroborated · file writes confirmed on disk.' : 'independently corroborated.';
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Grade a composed `SessionVerdict` into the headline the surface may show.
|
|
83
|
-
* Pure — no I/O. The single owner of "what is the ✓ allowed to say."
|
|
84
|
-
*/
|
|
85
|
-
export function gradeComposite(verdict) {
|
|
86
|
-
const signals = [];
|
|
87
|
-
// 1 · self-reported — the claim vs its own artifacts.
|
|
88
|
-
const cs = verdict.claim.status;
|
|
89
|
-
const claimState = cs === 'verified' ? 'pass' : cs === 'broken' ? 'fail' : cs === 'no-report' ? 'absent' : 'neutral';
|
|
90
|
-
signals.push({
|
|
91
|
-
name: 'self-report',
|
|
92
|
-
provenance: 'self-reported',
|
|
93
|
-
state: claimState,
|
|
94
|
-
detail: `the agent's report ${cs === 'verified' ? 'matches' : cs === 'broken' ? 'contradicts' : 'makes no checkable claim against'} its own recorded work`,
|
|
95
|
-
});
|
|
96
|
-
// 2 · observed — the audit's declared writes vs the real git diff.
|
|
97
|
-
const av = verdict.audit?.verdict;
|
|
98
|
-
if (av !== undefined) {
|
|
99
|
-
const auditState = av === 'consistent' ? 'pass' : 'neutral'; // 'unconfirmed' is honest under-count, never a fail
|
|
100
|
-
signals.push({
|
|
101
|
-
name: 'audit-on-disk',
|
|
102
|
-
provenance: 'observed',
|
|
103
|
-
state: auditState,
|
|
104
|
-
detail: av === 'consistent'
|
|
105
|
-
? 'declared file writes confirmed on disk'
|
|
106
|
-
: av === 'no-writes'
|
|
107
|
-
? 'no file writes to confirm'
|
|
108
|
-
: av === 'unconfirmed'
|
|
109
|
-
? "couldn't confirm every declared write"
|
|
110
|
-
: 'on-disk effect unobservable',
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
// 3 · adversarial — a different vendor independently reviewed it.
|
|
114
|
-
const cv = verdict.crossVendor;
|
|
115
|
-
if (cv !== undefined) {
|
|
116
|
-
const cvState = cv.proven ? 'pass' : 'neutral'; // not-proven ⇒ shown, never a fail (codehere doesn't adjudicate)
|
|
117
|
-
signals.push({
|
|
118
|
-
name: 'cross-vendor',
|
|
119
|
-
provenance: 'adversarial',
|
|
120
|
-
state: cvState,
|
|
121
|
-
detail: cv.proven
|
|
122
|
-
? `${cv.reviewer.agent} independently reviewed ${cv.writer.agent}'s work, integrity-checked`
|
|
123
|
-
: cv.crossVendor
|
|
124
|
-
? `cross-vendor review ran but isn't a clean proof — ${cv.caveats[0] ?? 'see caveats'}`
|
|
125
|
-
: 'not a cross-vendor review',
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
const independentPass = signals.some((s) => s.provenance !== 'self-reported' && s.state === 'pass');
|
|
129
|
-
const selfFailed = claimState === 'fail';
|
|
130
|
-
let headline;
|
|
131
|
-
if (cs === 'no-report')
|
|
132
|
-
headline = 'no-report';
|
|
133
|
-
else if (selfFailed)
|
|
134
|
-
headline = 'broken';
|
|
135
|
-
else if (cs === 'nothing-to-verify' && !independentPass)
|
|
136
|
-
headline = 'nothing-to-verify';
|
|
137
|
-
else if (independentPass)
|
|
138
|
-
headline = 'proven';
|
|
139
|
-
else
|
|
140
|
-
headline = 'reported';
|
|
141
|
-
const green = headline === 'proven';
|
|
142
|
-
const clause = corroborationClause({ signals });
|
|
143
|
-
const summary = headline === 'proven'
|
|
144
|
-
? clause.charAt(0).toUpperCase() + clause.slice(1)
|
|
145
|
-
: headline === 'broken'
|
|
146
|
-
? 'The report contradicts its own recorded work.'
|
|
147
|
-
: headline === 'reported'
|
|
148
|
-
? 'Self-reported only — no independent signal has corroborated it yet.'
|
|
149
|
-
: headline === 'nothing-to-verify'
|
|
150
|
-
? 'No checkable claim to verify.'
|
|
151
|
-
: 'No report to grade.';
|
|
152
|
-
return { headline, green, signals, summary };
|
|
153
|
-
}
|
|
154
|
-
//# sourceMappingURL=verdict-service.js.map
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-vendor verdict — the receipt a model lab structurally cannot sell.
|
|
3
|
-
*
|
|
4
|
-
* When one vendor wrote code and a DIFFERENT vendor reviewed it in the same
|
|
5
|
-
* room, this produces the proof-of-process: who wrote, who reviewed, what the
|
|
6
|
-
* audit chain attributes to each, and the combined cost. It is the output that
|
|
7
|
-
* turns the wedge from "two sessions happened to share a room" into a stated,
|
|
8
|
-
* checkable claim.
|
|
9
|
-
*
|
|
10
|
-
* HONESTY (the trap the wedge test already flagged, refuter-pinned):
|
|
11
|
-
* codehere does NOT adjudicate whether the reviewer found a REAL bug — it can't
|
|
12
|
-
* judge code correctness, and a generic "found a flaw" tripwire would read green
|
|
13
|
-
* on a review that found nothing. So this proves the PROCESS (a different vendor
|
|
14
|
-
* independently reviewed and recorded findings, integrity-checked) and surfaces
|
|
15
|
-
* the reviewer's findings verbatim for the human to judge. `proven` is true ONLY
|
|
16
|
-
* when it really is a cross-vendor review, the chain verifies, both vendors are
|
|
17
|
-
* attributed in it, and the review actually produced output. Anything short of
|
|
18
|
-
* that is stated plainly, never laundered into a green check.
|
|
19
|
-
*
|
|
20
|
-
* Wording stays "integrity-checked", never "tamper-evident" (domain/audit.ts:
|
|
21
|
-
* the unkeyed SHA-256 chain detects naive edits, not a determined re-walk).
|
|
22
|
-
*/
|
|
23
|
-
import { reconcileAudit } from './reconcile-audit.js';
|
|
24
|
-
import { estimateCostUsd } from '../../infrastructure/cost/spend-aggregator.js';
|
|
25
|
-
/** Read-ish tool names across adapters — used to report "reviewer read N files".
|
|
26
|
-
* Conservative: only names that are unambiguously a read. A reviewer SHOULD
|
|
27
|
-
* have only reads; any write is surfaced as a caveat, not hidden. */
|
|
28
|
-
const READ_TOOLS = new Set(['read', 'glob', 'grep', 'ls', 'cat', 'view', 'search_file', 'list_files'].map((t) => t.toLowerCase()));
|
|
29
|
-
/** Same WRITE_TOOLS spirit as reconcile-audit, kept local + minimal: we only
|
|
30
|
-
* need "did the reviewer mutate anything" (it shouldn't). */
|
|
31
|
-
const WRITE_TOOLS = new Set([
|
|
32
|
-
'write',
|
|
33
|
-
'edit',
|
|
34
|
-
'multiedit',
|
|
35
|
-
'notebookedit',
|
|
36
|
-
'delete',
|
|
37
|
-
'write_file',
|
|
38
|
-
'edit_file',
|
|
39
|
-
'create_file',
|
|
40
|
-
'str_replace',
|
|
41
|
-
'apply_patch',
|
|
42
|
-
].map((t) => t.toLowerCase()));
|
|
43
|
-
function countTools(entries, set) {
|
|
44
|
-
return entries.filter((e) => set.has((e.toolName || '').toLowerCase())).length;
|
|
45
|
-
}
|
|
46
|
-
function fmtUsd(n) {
|
|
47
|
-
return n === 0 ? '$0' : `$${n.toFixed(2)}`;
|
|
48
|
-
}
|
|
49
|
-
export function assessCrossVendor(input) {
|
|
50
|
-
const { writer, reviewer, entries, chain } = input;
|
|
51
|
-
const crossVendor = writer.agent !== reviewer.agent;
|
|
52
|
-
// Attribute by the sessionId the tool-call entries carry. For a direct run
|
|
53
|
-
// that's the run's own sessionId; for a conductor-routed run the audit hook
|
|
54
|
-
// keyed the entries by the SPECIALIST's runtime id (delegateSessionId), not
|
|
55
|
-
// the conductor task's id — so match EITHER (1.3). Without the delegate leg,
|
|
56
|
-
// every Auto-routed task reads "couldn't attribute" though its writes are in
|
|
57
|
-
// the chain. pre_prompt scans use a separate scan id and carry no write/read
|
|
58
|
-
// tool, so they never inflate counts; sessionId scoping stays precise and
|
|
59
|
-
// immune to mixing other rooms' same-vendor sessions.
|
|
60
|
-
const matchesRun = (e, run) => e.sessionId === run.sessionId || (run.delegateSessionId !== undefined && e.sessionId === run.delegateSessionId);
|
|
61
|
-
const writerEntries = entries.filter((e) => matchesRun(e, writer));
|
|
62
|
-
const reviewerEntries = entries.filter((e) => matchesRun(e, reviewer));
|
|
63
|
-
const writerRecon = reconcileAudit(writerEntries, writer.artifacts);
|
|
64
|
-
const reviewerRead = countTools(reviewerEntries, READ_TOOLS);
|
|
65
|
-
const reviewerWrote = countTools(reviewerEntries, WRITE_TOOLS);
|
|
66
|
-
const reviewHasFindings = !!(reviewer.reviewText && reviewer.reviewText.trim().length > 0);
|
|
67
|
-
const writerAttributed = writerEntries.length > 0;
|
|
68
|
-
const reviewerAttributed = reviewerEntries.length > 0;
|
|
69
|
-
const totalCostUsd = estimateCostUsd(writer.agent, writer.artifacts?.usage) + estimateCostUsd(reviewer.agent, reviewer.artifacts?.usage);
|
|
70
|
-
const caveats = [];
|
|
71
|
-
if (!crossVendor)
|
|
72
|
-
caveats.push(`Same vendor on both sides (${writer.agent}) — not a cross-vendor proof.`);
|
|
73
|
-
if (!chain.valid)
|
|
74
|
-
caveats.push("Audit chain didn't verify — the proof is withheld until it does.");
|
|
75
|
-
if (crossVendor && !writerAttributed)
|
|
76
|
-
caveats.push(`Couldn't attribute ${writer.agent}'s work in the audit chain.`);
|
|
77
|
-
if (crossVendor && !reviewerAttributed)
|
|
78
|
-
caveats.push(`Couldn't attribute ${reviewer.agent}'s review in the audit chain.`);
|
|
79
|
-
if (reviewerWrote > 0)
|
|
80
|
-
caveats.push(`${reviewer.agent} also changed ${reviewerWrote} file(s) — this wasn't a read-only review.`);
|
|
81
|
-
if (!reviewHasFindings)
|
|
82
|
-
caveats.push(`${reviewer.agent}'s review recorded no findings text.`);
|
|
83
|
-
// A clean cross-vendor REVIEW proof is read-only — if the reviewer also wrote,
|
|
84
|
-
// the receipt can't honestly say "changed nothing", so it isn't `proven`
|
|
85
|
-
// (the reviewerWrote caveat above explains why). Refuter 2026-06-13.
|
|
86
|
-
const proven = crossVendor && chain.valid && writerAttributed && reviewerAttributed && reviewHasFindings && reviewerWrote === 0;
|
|
87
|
-
const summary = proven
|
|
88
|
-
? `${reviewer.agent} reviewed ${writer.agent}'s work in this room. ` +
|
|
89
|
-
`${writer.agent} ${writerRecon.verdict === 'consistent' ? `wrote ${writerRecon.filesObserved} file(s), confirmed on disk` : `recorded ${writerRecon.writeToolCalls} write(s)`}; ` +
|
|
90
|
-
`${reviewer.agent} read ${reviewerRead}, changed nothing. ` +
|
|
91
|
-
`Audit chain: ${chain.chainedEntries}/${chain.totalEntries} integrity-checked, both vendors attributed. ` +
|
|
92
|
-
`Combined cost: ${fmtUsd(totalCostUsd)}.`
|
|
93
|
-
: crossVendor
|
|
94
|
-
? `${reviewer.agent} reviewed ${writer.agent}'s work, but this isn't a clean cross-vendor proof yet — ${caveats[0]}`
|
|
95
|
-
: caveats[0];
|
|
96
|
-
return {
|
|
97
|
-
crossVendor,
|
|
98
|
-
proven,
|
|
99
|
-
writer: {
|
|
100
|
-
agent: writer.agent,
|
|
101
|
-
sessionId: writer.sessionId,
|
|
102
|
-
wrote: writerRecon.writeToolCalls,
|
|
103
|
-
reconciliation: writerRecon.verdict,
|
|
104
|
-
reconNote: writerRecon.note,
|
|
105
|
-
},
|
|
106
|
-
reviewer: {
|
|
107
|
-
agent: reviewer.agent,
|
|
108
|
-
sessionId: reviewer.sessionId,
|
|
109
|
-
read: reviewerRead,
|
|
110
|
-
wrote: reviewerWrote,
|
|
111
|
-
hasFindings: reviewHasFindings,
|
|
112
|
-
},
|
|
113
|
-
chain: {
|
|
114
|
-
totalEntries: chain.totalEntries,
|
|
115
|
-
chainedEntries: chain.chainedEntries,
|
|
116
|
-
valid: chain.valid,
|
|
117
|
-
writerAttributed,
|
|
118
|
-
reviewerAttributed,
|
|
119
|
-
},
|
|
120
|
-
totalCostUsd,
|
|
121
|
-
summary,
|
|
122
|
-
caveats,
|
|
123
|
-
reviewText: reviewer.reviewText,
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
//# sourceMappingURL=assess-cross-vendor.js.map
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gather the live data a cross-vendor verdict needs (room sessions, audit
|
|
3
|
-
* entries, chain facts) and run the pure `assessCrossVendor`. Shared by the CLI
|
|
4
|
-
* (`codehere verify`) and the daemon (GET /api/task/:id/cross-vendor) so both
|
|
5
|
-
* surfaces compute the receipt the same way — one path, no drift.
|
|
6
|
-
*
|
|
7
|
-
* The pure judgment lives in assess-cross-vendor.ts; this is the (disk-reading)
|
|
8
|
-
* glue around it.
|
|
9
|
-
*
|
|
10
|
-
* PAIRING is OBSERVED, not inferred: a reviewer records the writer it reviewed
|
|
11
|
-
* (parentSessionId, stamped at spawn). Older / manual reviews that predate that
|
|
12
|
-
* ground truth fall back to a STRICT inference — a candidate is only treated as
|
|
13
|
-
* a reviewer if it changed NO files (a read-only review). A session that wrote
|
|
14
|
-
* files is a writer, never a reviewer; inferring otherwise mispaired in a
|
|
15
|
-
* multi-run room (a later writer read as a reviewer of an earlier one).
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync, readFileSync } from 'fs';
|
|
18
|
-
import { loadRecent, loadSessionById, } from '../../infrastructure/orchestration/session-persistence.js';
|
|
19
|
-
import { AUDIT_FILE, verifyAuditChain } from '../../infrastructure/hooks/claude-sdk-hook.js';
|
|
20
|
-
import { assessCrossVendor } from './assess-cross-vendor.js';
|
|
21
|
-
/** Assemble the verdict for a KNOWN writer↔reviewer pair (the role decision is
|
|
22
|
-
* the caller's; this just reads the chain + runs the pure assessment). */
|
|
23
|
-
function assembleVerdict(writer, reviewer) {
|
|
24
|
-
let entries = [];
|
|
25
|
-
if (existsSync(AUDIT_FILE)) {
|
|
26
|
-
entries = readFileSync(AUDIT_FILE, 'utf-8')
|
|
27
|
-
.split('\n')
|
|
28
|
-
.filter(Boolean)
|
|
29
|
-
.map((l) => {
|
|
30
|
-
try {
|
|
31
|
-
return JSON.parse(l);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
.filter((e) => !!e);
|
|
38
|
-
}
|
|
39
|
-
const chain = verifyAuditChain(AUDIT_FILE);
|
|
40
|
-
const reviewText = reviewer.finalReport?.summary?.trim() ||
|
|
41
|
-
(reviewer.steps ?? [])
|
|
42
|
-
.filter((s) => s.kind === 'say')
|
|
43
|
-
.map((s) => s.label)
|
|
44
|
-
.join('\n')
|
|
45
|
-
.trim() ||
|
|
46
|
-
undefined;
|
|
47
|
-
return assessCrossVendor({
|
|
48
|
-
writer: {
|
|
49
|
-
agent: writer.agent,
|
|
50
|
-
sessionId: writer.sessionId,
|
|
51
|
-
delegateSessionId: writer.delegateSessionId,
|
|
52
|
-
artifacts: writer.artifacts,
|
|
53
|
-
},
|
|
54
|
-
reviewer: {
|
|
55
|
-
agent: reviewer.agent,
|
|
56
|
-
sessionId: reviewer.sessionId,
|
|
57
|
-
delegateSessionId: reviewer.delegateSessionId,
|
|
58
|
-
artifacts: reviewer.artifacts,
|
|
59
|
-
reviewText,
|
|
60
|
-
},
|
|
61
|
-
entries,
|
|
62
|
-
chain: { totalEntries: chain.totalEntries, chainedEntries: chain.chainedEntries, valid: chain.valid },
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
/** Build the cross-vendor verdict for a session that REVIEWED an earlier writer
|
|
66
|
-
* in the same room. Returns undefined when there's no such pair (the session
|
|
67
|
-
* isn't a cross-vendor review). */
|
|
68
|
-
export function buildCrossVendorVerdict(reviewer) {
|
|
69
|
-
if (!reviewer.cwd)
|
|
70
|
-
return undefined;
|
|
71
|
-
// OBSERVED link: an auto-review records the exact writer it reviewed. Gate on
|
|
72
|
-
// isReview, NOT parentSessionId alone — parentSessionId is overloaded (the room
|
|
73
|
-
// composer also sets it when a goal is HANDED OFF to a different vendor to
|
|
74
|
-
// CONTINUE the work, which is not a review). Only isReview means "this session
|
|
75
|
-
// reviewed its parent." Without this gate a continuation would be falsely
|
|
76
|
-
// labeled "X reviewed Y's work."
|
|
77
|
-
if (reviewer.isReview && reviewer.parentSessionId) {
|
|
78
|
-
const writer = loadSessionById(reviewer.parentSessionId);
|
|
79
|
-
if (!writer || writer.agent === reviewer.agent)
|
|
80
|
-
return undefined;
|
|
81
|
-
return assembleVerdict(writer, reviewer);
|
|
82
|
-
}
|
|
83
|
-
// LEGACY / manual: no recorded link. Only a READ-ONLY session (changed no
|
|
84
|
-
// files) can be a reviewer — a session that wrote files is a writer. This is
|
|
85
|
-
// the guard that stops a later writer being mispaired as a reviewer.
|
|
86
|
-
if ((reviewer.artifacts?.files?.length ?? 0) > 0)
|
|
87
|
-
return undefined;
|
|
88
|
-
const writer = loadRecent(50)
|
|
89
|
-
.filter((s) => s.cwd === reviewer.cwd &&
|
|
90
|
-
s.sessionId !== reviewer.sessionId &&
|
|
91
|
-
s.startedAt < reviewer.startedAt &&
|
|
92
|
-
s.agent !== reviewer.agent &&
|
|
93
|
-
(s.artifacts?.files?.length ?? 0) > 0)
|
|
94
|
-
.sort((a, b) => b.startedAt - a.startedAt)[0];
|
|
95
|
-
if (!writer)
|
|
96
|
-
return undefined;
|
|
97
|
-
return assembleVerdict(writer, reviewer);
|
|
98
|
-
}
|
|
99
|
-
/** Convenience for the daemon route: load the session by id, then build. */
|
|
100
|
-
export function buildCrossVendorVerdictById(sessionId) {
|
|
101
|
-
const reviewer = loadSessionById(sessionId);
|
|
102
|
-
return reviewer ? buildCrossVendorVerdict(reviewer) : undefined;
|
|
103
|
-
}
|
|
104
|
-
/** The WRITER-keyed mirror: given a writer, find the reviewer that reviewed IT
|
|
105
|
-
* and build the verdict. A user opens the WRITER's run and wants to see "a
|
|
106
|
-
* different vendor reviewed this"; the reviewer recorded the link, so we read
|
|
107
|
-
* it directly (parentSessionId === this writer + isReview) — no inference, no
|
|
108
|
-
* mispairing. Returns undefined when no review of this writer exists. */
|
|
109
|
-
export function buildCrossVendorVerdictForWriter(writerId) {
|
|
110
|
-
const writer = loadSessionById(writerId);
|
|
111
|
-
if (!writer || !writer.cwd)
|
|
112
|
-
return undefined;
|
|
113
|
-
const reviewer = loadRecent(50).find((s) => s.isReview === true && s.parentSessionId === writerId);
|
|
114
|
-
if (!reviewer || reviewer.agent === writer.agent)
|
|
115
|
-
return undefined;
|
|
116
|
-
return assembleVerdict(writer, reviewer);
|
|
117
|
-
}
|
|
118
|
-
//# sourceMappingURL=build-cross-vendor-verdict.js.map
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-domain trust grade for W-PASS write targets · v0.9 must-ship.
|
|
3
|
-
*
|
|
4
|
-
* Demo v12 + locked v1 tier scope: "linear.app A · *.atlassian.net B ·
|
|
5
|
-
* unknown F · first-write confirmation per domain."
|
|
6
|
-
*
|
|
7
|
-
* Two pieces of state:
|
|
8
|
-
* 1. WELL_KNOWN_GRADES · hardcoded · the platforms we've vetted ourselves
|
|
9
|
-
* ship with grade A. Subdomain wildcards get B. Everything else
|
|
10
|
-
* defaults to F (forces explicit user confirmation on first write).
|
|
11
|
-
* 2. USER_TRUSTED_DOMAINS · ~/.codehere/keys/trusted-domains.json ·
|
|
12
|
-
* domains the user has explicitly trusted after a first-write prompt.
|
|
13
|
-
* Domains here promote from F → B.
|
|
14
|
-
*
|
|
15
|
-
* Pure logic. CLI + HTTP wrap it.
|
|
16
|
-
*/
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
18
|
-
import { homedir } from 'os';
|
|
19
|
-
import { dirname, join } from 'path';
|
|
20
|
-
const WELL_KNOWN_GRADES = [
|
|
21
|
-
// Grade A · platforms codehere has shipped+tested adapters for
|
|
22
|
-
{ pattern: /^api\.linear\.app$/i, grade: 'A', reason: 'official Linear API · codehere ships a vetted adapter' },
|
|
23
|
-
{ pattern: /^api\.github\.com$/i, grade: 'A', reason: 'official GitHub API · codehere ships a vetted adapter' },
|
|
24
|
-
{ pattern: /^api\.figma\.com$/i, grade: 'A', reason: 'official Figma API · codehere ships a vetted adapter' },
|
|
25
|
-
// Grade B · same vendor, surrounding subdomains
|
|
26
|
-
{ pattern: /^[a-z0-9-]+\.linear\.app$/i, grade: 'B', reason: 'Linear subdomain · adjacent to the vetted API' },
|
|
27
|
-
{ pattern: /^[a-z0-9-]+\.github\.com$/i, grade: 'B', reason: 'GitHub subdomain · adjacent to the vetted API' },
|
|
28
|
-
{ pattern: /^[a-z0-9-]+\.figma\.com$/i, grade: 'B', reason: 'Figma subdomain · adjacent to the vetted API' },
|
|
29
|
-
{
|
|
30
|
-
pattern: /^[a-z0-9-]+\.atlassian\.net$/i,
|
|
31
|
-
grade: 'B',
|
|
32
|
-
reason: 'Atlassian-hosted instance · vendor known but instance not vetted',
|
|
33
|
-
},
|
|
34
|
-
// Grade C · public hosts known to be commonly used
|
|
35
|
-
{ pattern: /^raw\.githubusercontent\.com$/i, grade: 'C', reason: 'GitHub-hosted raw content · static, read-mostly' },
|
|
36
|
-
];
|
|
37
|
-
function trustedDomainsPath() {
|
|
38
|
-
const home = process.env.CODEHERE_HOME || homedir();
|
|
39
|
-
return join(home, '.codehere', 'keys', 'trusted-domains.json');
|
|
40
|
-
}
|
|
41
|
-
export function loadTrustedDomains() {
|
|
42
|
-
const path = trustedDomainsPath();
|
|
43
|
-
if (!existsSync(path))
|
|
44
|
-
return { schema: 1, domains: {} };
|
|
45
|
-
try {
|
|
46
|
-
const raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
47
|
-
if (raw && raw.schema === 1 && typeof raw.domains === 'object')
|
|
48
|
-
return raw;
|
|
49
|
-
}
|
|
50
|
-
catch {
|
|
51
|
-
/* fall through · empty trust store */
|
|
52
|
-
}
|
|
53
|
-
return { schema: 1, domains: {} };
|
|
54
|
-
}
|
|
55
|
-
export function trustDomain(host, note) {
|
|
56
|
-
const file = loadTrustedDomains();
|
|
57
|
-
file.domains[host.toLowerCase()] = { addedAt: Date.now(), note };
|
|
58
|
-
mkdirSync(dirname(trustedDomainsPath()), { recursive: true });
|
|
59
|
-
writeFileSync(trustedDomainsPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
|
|
60
|
-
}
|
|
61
|
-
export function untrustDomain(host) {
|
|
62
|
-
const file = loadTrustedDomains();
|
|
63
|
-
const key = host.toLowerCase();
|
|
64
|
-
if (!file.domains[key])
|
|
65
|
-
return false;
|
|
66
|
-
delete file.domains[key];
|
|
67
|
-
writeFileSync(trustedDomainsPath(), JSON.stringify(file, null, 2) + '\n', 'utf-8');
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
export function gradeDomain(host) {
|
|
71
|
-
const userFile = loadTrustedDomains();
|
|
72
|
-
const userTrusted = !!userFile.domains[host.toLowerCase()];
|
|
73
|
-
for (const rule of WELL_KNOWN_GRADES) {
|
|
74
|
-
if (rule.pattern.test(host)) {
|
|
75
|
-
return {
|
|
76
|
-
host,
|
|
77
|
-
grade: rule.grade,
|
|
78
|
-
reason: rule.reason,
|
|
79
|
-
userTrusted,
|
|
80
|
-
requiresFirstWriteConfirmation: false,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (userTrusted) {
|
|
85
|
-
return {
|
|
86
|
-
host,
|
|
87
|
-
grade: 'B',
|
|
88
|
-
reason: 'previously trusted by you · stored in trusted-domains.json',
|
|
89
|
-
userTrusted: true,
|
|
90
|
-
requiresFirstWriteConfirmation: false,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return {
|
|
94
|
-
host,
|
|
95
|
-
grade: 'F',
|
|
96
|
-
reason: 'unknown domain · not in well-known list · not previously trusted',
|
|
97
|
-
userTrusted: false,
|
|
98
|
-
requiresFirstWriteConfirmation: true,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/** Extract the host from a fully-resolved HTTPS URL. */
|
|
102
|
-
export function hostOf(url) {
|
|
103
|
-
try {
|
|
104
|
-
return new URL(url).host;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
return '';
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
//# sourceMappingURL=domain-trust.js.map
|