@intentic/sandbox-contract 1.176.2 → 1.209.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/README.md +65 -0
- package/dist/agent-catalog.d.ts +15 -2
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/agent-catalog.js +50 -1
- package/dist/agent-catalog.js.map +1 -1
- package/dist/agent-run-model.d.ts +3 -0
- package/dist/agent-run-model.d.ts.map +1 -0
- package/dist/agent-run-model.js +10 -0
- package/dist/agent-run-model.js.map +1 -0
- package/dist/capability-env.d.ts +2 -0
- package/dist/capability-env.d.ts.map +1 -0
- package/dist/capability-env.js +2 -0
- package/dist/capability-env.js.map +1 -0
- package/dist/chores/chores.d.ts +9 -0
- package/dist/chores/chores.d.ts.map +1 -1
- package/dist/chores/chores.js +257 -18
- package/dist/chores/chores.js.map +1 -1
- package/dist/chores/digest.js +0 -0
- package/dist/chores/digest.js.map +1 -1
- package/dist/chores/extension-update.d.ts +9 -0
- package/dist/chores/extension-update.d.ts.map +1 -0
- package/dist/chores/extension-update.js +15 -0
- package/dist/chores/extension-update.js.map +1 -0
- package/dist/chores/fix-deps.d.ts +10 -0
- package/dist/chores/fix-deps.d.ts.map +1 -0
- package/dist/chores/fix-deps.js +18 -0
- package/dist/chores/fix-deps.js.map +1 -0
- package/dist/chores/index.d.ts +7 -2
- package/dist/chores/index.d.ts.map +1 -1
- package/dist/chores/index.js +4 -1
- package/dist/chores/index.js.map +1 -1
- package/dist/chores/probes.d.ts +1 -0
- package/dist/chores/probes.d.ts.map +1 -1
- package/dist/chores/probes.js +131 -3
- package/dist/chores/probes.js.map +1 -1
- package/dist/chores/stack.d.ts +27 -0
- package/dist/chores/stack.d.ts.map +1 -0
- package/dist/chores/stack.js +121 -0
- package/dist/chores/stack.js.map +1 -0
- package/dist/chores/verdict.d.ts +2 -1
- package/dist/chores/verdict.d.ts.map +1 -1
- package/dist/chores/verdict.js +57 -7
- package/dist/chores/verdict.js.map +1 -1
- package/dist/contract-lock.d.ts +3 -0
- package/dist/contract-lock.d.ts.map +1 -0
- package/dist/contract-lock.js +35 -0
- package/dist/contract-lock.js.map +1 -0
- package/dist/contracts/activity.contract.d.ts +10 -0
- package/dist/contracts/activity.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.d.ts +230 -20
- package/dist/contracts/agent.contract.d.ts.map +1 -1
- package/dist/contracts/agent.contract.js +2 -1
- package/dist/contracts/agent.contract.js.map +1 -1
- package/dist/contracts/agents.contract.d.ts +847 -1
- package/dist/contracts/agents.contract.d.ts.map +1 -1
- package/dist/contracts/agents.contract.js +3 -1
- package/dist/contracts/agents.contract.js.map +1 -1
- package/dist/contracts/automations.contract.d.ts +89 -0
- package/dist/contracts/automations.contract.d.ts.map +1 -1
- package/dist/contracts/automations.contract.js +3 -1
- package/dist/contracts/automations.contract.js.map +1 -1
- package/dist/contracts/capabilities.contract.d.ts +108 -32
- package/dist/contracts/capabilities.contract.d.ts.map +1 -1
- package/dist/contracts/capabilities.contract.js +5 -1
- package/dist/contracts/capabilities.contract.js.map +1 -1
- package/dist/contracts/chores.contract.d.ts +43 -0
- package/dist/contracts/chores.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.d.ts +5 -0
- package/dist/contracts/ci.contract.d.ts.map +1 -1
- package/dist/contracts/ci.contract.js +2 -2
- package/dist/contracts/ci.contract.js.map +1 -1
- package/dist/contracts/claude.contract.d.ts +3 -14
- package/dist/contracts/claude.contract.d.ts.map +1 -1
- package/dist/contracts/claude.contract.js +2 -3
- package/dist/contracts/claude.contract.js.map +1 -1
- package/dist/contracts/drafts.contract.d.ts +4 -0
- package/dist/contracts/drafts.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.d.ts +28 -12
- package/dist/contracts/endpoints.contract.d.ts.map +1 -1
- package/dist/contracts/endpoints.contract.js +9 -0
- package/dist/contracts/endpoints.contract.js.map +1 -1
- package/dist/contracts/extensions.contract.d.ts +399 -69
- package/dist/contracts/extensions.contract.d.ts.map +1 -1
- package/dist/contracts/extensions.contract.js +15 -1
- package/dist/contracts/extensions.contract.js.map +1 -1
- package/dist/contracts/git.contract.d.ts +209 -11
- package/dist/contracts/git.contract.d.ts.map +1 -1
- package/dist/contracts/git.contract.js +12 -2
- package/dist/contracts/git.contract.js.map +1 -1
- package/dist/contracts/grok.contract.d.ts +0 -13
- package/dist/contracts/grok.contract.d.ts.map +1 -1
- package/dist/contracts/grok.contract.js +1 -2
- package/dist/contracts/grok.contract.js.map +1 -1
- package/dist/contracts/host.contract.d.ts +39 -0
- package/dist/contracts/host.contract.d.ts.map +1 -1
- package/dist/contracts/host.contract.js +3 -2
- package/dist/contracts/host.contract.js.map +1 -1
- package/dist/contracts/loops.contract.d.ts +343 -0
- package/dist/contracts/loops.contract.d.ts.map +1 -0
- package/dist/contracts/loops.contract.js +11 -0
- package/dist/contracts/loops.contract.js.map +1 -0
- package/dist/contracts/panels.contract.d.ts +6 -0
- package/dist/contracts/panels.contract.d.ts.map +1 -1
- package/dist/contracts/personas.contract.d.ts +112 -0
- package/dist/contracts/personas.contract.d.ts.map +1 -0
- package/dist/contracts/personas.contract.js +13 -0
- package/dist/contracts/personas.contract.js.map +1 -0
- package/dist/contracts/ports.contract.d.ts +1 -0
- package/dist/contracts/ports.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.d.ts +1 -0
- package/dist/contracts/prepush.contract.d.ts.map +1 -1
- package/dist/contracts/prepush.contract.js.map +1 -1
- package/dist/contracts/{codex.contract.d.ts → providers.contract.d.ts} +11 -3
- package/dist/contracts/providers.contract.d.ts.map +1 -0
- package/dist/contracts/providers.contract.js +6 -0
- package/dist/contracts/providers.contract.js.map +1 -0
- package/dist/contracts/public.contract.d.ts +24 -0
- package/dist/contracts/public.contract.d.ts.map +1 -0
- package/dist/contracts/public.contract.js +8 -0
- package/dist/contracts/public.contract.js.map +1 -0
- package/dist/contracts/push.contract.d.ts +3 -3
- package/dist/contracts/secrets.contract.d.ts +9 -0
- package/dist/contracts/secrets.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.d.ts +16 -1
- package/dist/contracts/sessions.contract.d.ts.map +1 -1
- package/dist/contracts/sessions.contract.js +1 -1
- package/dist/contracts/sessions.contract.js.map +1 -1
- package/dist/contracts/settings.contract.d.ts +352 -58
- package/dist/contracts/settings.contract.d.ts.map +1 -1
- package/dist/contracts/settings.contract.js +2 -1
- package/dist/contracts/settings.contract.js.map +1 -1
- package/dist/contracts/share.contract.d.ts +55 -0
- package/dist/contracts/share.contract.d.ts.map +1 -0
- package/dist/contracts/share.contract.js +9 -0
- package/dist/contracts/share.contract.js.map +1 -0
- package/dist/contracts/skills.contract.d.ts +41 -0
- package/dist/contracts/skills.contract.d.ts.map +1 -0
- package/dist/contracts/skills.contract.js +9 -0
- package/dist/contracts/skills.contract.js.map +1 -0
- package/dist/contracts/system.contract.d.ts +218 -22
- package/dist/contracts/system.contract.d.ts.map +1 -1
- package/dist/contracts/system.contract.js +10 -2
- package/dist/contracts/system.contract.js.map +1 -1
- package/dist/contracts/workflows.contract.d.ts +575 -0
- package/dist/contracts/workflows.contract.d.ts.map +1 -0
- package/dist/contracts/workflows.contract.js +13 -0
- package/dist/contracts/workflows.contract.js.map +1 -0
- package/dist/contracts/workspace.contract.d.ts +18 -4
- package/dist/contracts/workspace.contract.d.ts.map +1 -1
- package/dist/contracts/workspace.contract.js +6 -3
- package/dist/contracts/workspace.contract.js.map +1 -1
- package/dist/conversation-ids.d.ts +2 -0
- package/dist/conversation-ids.d.ts.map +1 -0
- package/dist/conversation-ids.js +113 -0
- package/dist/conversation-ids.js.map +1 -0
- package/dist/events.d.ts +523 -17
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +106 -13
- package/dist/events.js.map +1 -1
- package/dist/history-state.d.ts +3 -0
- package/dist/history-state.d.ts.map +1 -0
- package/dist/history-state.js +53 -0
- package/dist/history-state.js.map +1 -0
- package/dist/hostnames.d.ts +4 -0
- package/dist/hostnames.d.ts.map +1 -1
- package/dist/hostnames.js +7 -2
- package/dist/hostnames.js.map +1 -1
- package/dist/index.d.ts +3793 -478
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -17
- package/dist/index.js.map +1 -1
- package/dist/listener-protocol.d.ts +77 -0
- package/dist/listener-protocol.d.ts.map +1 -0
- package/dist/listener-protocol.js +33 -0
- package/dist/listener-protocol.js.map +1 -0
- package/dist/model-order.d.ts +1 -0
- package/dist/model-order.d.ts.map +1 -1
- package/dist/model-order.js +16 -0
- package/dist/model-order.js.map +1 -1
- package/dist/output-fields.d.ts +30 -0
- package/dist/output-fields.d.ts.map +1 -0
- package/dist/output-fields.js +57 -0
- package/dist/output-fields.js.map +1 -0
- package/dist/path-refs.d.ts.map +1 -1
- package/dist/path-refs.js.map +1 -1
- package/dist/publish-drafts.d.ts +7 -0
- package/dist/publish-drafts.d.ts.map +1 -0
- package/dist/publish-drafts.js +20 -0
- package/dist/publish-drafts.js.map +1 -0
- package/dist/quick-model.d.ts +2 -1
- package/dist/quick-model.d.ts.map +1 -1
- package/dist/quick-model.js +18 -14
- package/dist/quick-model.js.map +1 -1
- package/dist/routes.d.ts +3 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +63 -0
- package/dist/routes.js.map +1 -1
- package/dist/runtime-state.d.ts +33 -0
- package/dist/runtime-state.d.ts.map +1 -0
- package/dist/runtime-state.js +14 -0
- package/dist/runtime-state.js.map +1 -0
- package/dist/schemas.d.ts +4938 -866
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +1011 -192
- package/dist/schemas.js.map +1 -1
- package/dist/search-globs.d.ts +6 -0
- package/dist/search-globs.d.ts.map +1 -0
- package/dist/search-globs.js +39 -0
- package/dist/search-globs.js.map +1 -0
- package/dist/share-paths.d.ts +8 -0
- package/dist/share-paths.d.ts.map +1 -0
- package/dist/share-paths.js +21 -0
- package/dist/share-paths.js.map +1 -0
- package/dist/state-portability.d.ts +8 -0
- package/dist/state-portability.d.ts.map +1 -0
- package/dist/state-portability.js +4 -0
- package/dist/state-portability.js.map +1 -0
- package/dist/title.d.ts.map +1 -1
- package/dist/title.js +1 -1
- package/dist/title.js.map +1 -1
- package/dist/tunnel-ids.d.ts +1 -0
- package/dist/tunnel-ids.d.ts.map +1 -1
- package/dist/tunnel-ids.js +1 -0
- package/dist/tunnel-ids.js.map +1 -1
- package/dist/versions.d.ts +4 -0
- package/dist/versions.d.ts.map +1 -0
- package/dist/versions.js +15 -0
- package/dist/versions.js.map +1 -0
- package/dist/workflow-faults.d.ts +4 -0
- package/dist/workflow-faults.d.ts.map +1 -0
- package/dist/workflow-faults.js +115 -0
- package/dist/workflow-faults.js.map +1 -0
- package/dist/workspace-state.d.ts +282 -3
- package/dist/workspace-state.d.ts.map +1 -1
- package/dist/workspace-state.js +198 -12
- package/dist/workspace-state.js.map +1 -1
- package/package.json +30 -6
- package/src/agent-catalog.test.ts +147 -7
- package/src/agent-catalog.ts +233 -21
- package/src/agent-run-model.test.ts +60 -0
- package/src/agent-run-model.ts +51 -0
- package/src/capability-env.ts +10 -0
- package/src/capability-ledger.test.ts +110 -0
- package/src/chores/chores.test.ts +32 -0
- package/src/chores/chores.ts +456 -32
- package/src/chores/digest.ts +0 -0
- package/src/chores/extension-update.ts +37 -0
- package/src/chores/fix-deps.ts +40 -0
- package/src/chores/index.ts +7 -2
- package/src/chores/probes.test.ts +155 -3
- package/src/chores/probes.ts +209 -4
- package/src/chores/stack.test.ts +126 -0
- package/src/chores/stack.ts +244 -0
- package/src/chores/verdict.test.ts +357 -19
- package/src/chores/verdict.ts +102 -22
- package/src/contract-lock.test.ts +17 -0
- package/src/contract-lock.ts +66 -0
- package/src/contracts/agent.contract.ts +6 -0
- package/src/contracts/agents.contract.ts +12 -0
- package/src/contracts/automations.contract.ts +17 -1
- package/src/contracts/capabilities.contract.ts +22 -0
- package/src/contracts/ci.contract.ts +10 -2
- package/src/contracts/claude.contract.ts +8 -4
- package/src/contracts/endpoints.contract.ts +21 -0
- package/src/contracts/extensions.contract.ts +42 -0
- package/src/contracts/git.contract.ts +33 -6
- package/src/contracts/grok.contract.ts +4 -3
- package/src/contracts/host.contract.ts +13 -2
- package/src/contracts/loops.contract.ts +62 -0
- package/src/contracts/personas.contract.ts +56 -0
- package/src/contracts/prepush.contract.ts +4 -3
- package/src/contracts/providers.contract.ts +23 -0
- package/src/contracts/public.contract.ts +14 -0
- package/src/contracts/sessions.contract.ts +5 -1
- package/src/contracts/settings.contract.ts +5 -0
- package/src/contracts/share.contract.ts +20 -0
- package/src/contracts/skills.contract.ts +24 -0
- package/src/contracts/system.contract.ts +27 -3
- package/src/contracts/workflows.contract.ts +78 -0
- package/src/contracts/workspace.contract.ts +10 -2
- package/src/conversation-ids.test.ts +25 -0
- package/src/conversation-ids.ts +160 -0
- package/src/events.test.ts +20 -1
- package/src/events.ts +404 -43
- package/src/history-state.ts +129 -0
- package/src/hostnames.ts +20 -6
- package/src/index.ts +60 -17
- package/src/listener-protocol.ts +96 -0
- package/src/model-order.test.ts +32 -1
- package/src/model-order.ts +56 -3
- package/src/output-fields.ts +111 -0
- package/src/path-refs.test.ts +4 -4
- package/src/path-refs.ts +5 -5
- package/src/publish-drafts.ts +59 -0
- package/src/quick-model.test.ts +70 -27
- package/src/quick-model.ts +76 -40
- package/src/routes.test.ts +91 -3
- package/src/routes.ts +135 -0
- package/src/runtime-state.test.ts +45 -0
- package/src/runtime-state.ts +124 -0
- package/src/schemas.test.ts +16 -29
- package/src/schemas.ts +3526 -553
- package/src/search-globs.test.ts +46 -0
- package/src/search-globs.ts +76 -0
- package/src/share-paths.test.ts +48 -0
- package/src/share-paths.ts +68 -0
- package/src/state-portability.ts +56 -0
- package/src/title.test.ts +20 -1
- package/src/title.ts +12 -3
- package/src/tunnel-ids.ts +15 -1
- package/src/versions.test.ts +39 -0
- package/src/versions.ts +48 -0
- package/src/workflow-faults.test.ts +127 -0
- package/src/workflow-faults.ts +188 -0
- package/src/workspace-state.test.ts +189 -17
- package/src/workspace-state.ts +500 -30
- package/dist/contracts/codex.contract.d.ts.map +0 -1
- package/dist/contracts/codex.contract.js +0 -6
- package/dist/contracts/codex.contract.js.map +0 -1
- package/dist/contracts/gemini.contract.d.ts +0 -16
- package/dist/contracts/gemini.contract.d.ts.map +0 -1
- package/dist/contracts/gemini.contract.js +0 -6
- package/dist/contracts/gemini.contract.js.map +0 -1
- package/dist/contracts/kimi.contract.d.ts +0 -16
- package/dist/contracts/kimi.contract.d.ts.map +0 -1
- package/dist/contracts/kimi.contract.js +0 -6
- package/dist/contracts/kimi.contract.js.map +0 -1
- package/dist/contracts/komodo.contract.d.ts +0 -125
- package/dist/contracts/komodo.contract.d.ts.map +0 -1
- package/dist/contracts/komodo.contract.js +0 -14
- package/dist/contracts/komodo.contract.js.map +0 -1
- package/dist/contracts/memory.contract.d.ts +0 -34
- package/dist/contracts/memory.contract.d.ts.map +0 -1
- package/dist/contracts/memory.contract.js +0 -9
- package/dist/contracts/memory.contract.js.map +0 -1
- package/src/contracts/codex.contract.ts +0 -10
- package/src/contracts/gemini.contract.ts +0 -11
- package/src/contracts/kimi.contract.ts +0 -9
- package/src/contracts/komodo.contract.ts +0 -36
- package/src/contracts/memory.contract.ts +0 -13
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChoreLedgerEntry, ChorePackage, ChoresReport, ChoreShape, ChoreSignals, ProbeResult } from "../schemas.js";
|
|
1
|
+
import type { Bundle, ChoreLedgerEntry, ChorePackage, ChoresReport, ChoreShape, ChoreSignals, Duplication, ProbeResult, UiScan } from "../schemas.js";
|
|
2
2
|
import { describe, expect, test } from "vitest";
|
|
3
3
|
import { choreById, CHORES } from "./chores.js";
|
|
4
4
|
import { assessReport, ledgerKey, unseenVerdicts } from "./verdict.js";
|
|
@@ -20,14 +20,15 @@ const pkg = (over: Partial<ChorePackage> = {}): ChorePackage => ({
|
|
|
20
20
|
...over,
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
// A repository that is a Node workspace with documents, a pipeline
|
|
24
|
-
// default and each applicability test can turn off exactly the one fact it is about.
|
|
23
|
+
// A repository that is a Node workspace with documents, a pipeline, an image and a Tailwind front-end — so every
|
|
24
|
+
// chore APPLIES by default and each applicability test can turn off exactly the one fact it is about.
|
|
25
25
|
const shape = (over: Partial<ChoreShape> = {}): ChoreShape => ({
|
|
26
26
|
docs: [`docs/architecture/repo.md`],
|
|
27
27
|
dockerfiles: [`Dockerfile`],
|
|
28
28
|
ci: [`.github/workflows/ci.yml`],
|
|
29
29
|
lockfile: true,
|
|
30
30
|
packageManifest: true,
|
|
31
|
+
deps: [`tailwindcss`, `vue`],
|
|
31
32
|
...over,
|
|
32
33
|
});
|
|
33
34
|
|
|
@@ -46,10 +47,21 @@ const probe = (over: Partial<ProbeResult> & Pick<ProbeResult, "id">): ProbeResul
|
|
|
46
47
|
const auditProbe = (names: readonly string[]): ProbeResult =>
|
|
47
48
|
probe({
|
|
48
49
|
id: `audit`,
|
|
49
|
-
facts: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
facts: {
|
|
51
|
+
id: `audit`,
|
|
52
|
+
advisories: names.map((name) => ({ name, severity: `high` as const, title: `${name} is bad`, patched: `>=2`, dev: false })),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const report = (over: Partial<ChoresReport> = {}): ChoresReport => ({
|
|
57
|
+
repos: [{ repo: `app`, probes: [], signals: signals() }],
|
|
58
|
+
ledger: [],
|
|
59
|
+
// Verdicts are about EVIDENCE, never about work in flight — a probe running does not make a chore more or
|
|
60
|
+
// less due, it only makes the panel say so. Empty here because no assertion in this file should depend on it.
|
|
61
|
+
running: [],
|
|
62
|
+
node: `v24.18.0`,
|
|
63
|
+
...over,
|
|
64
|
+
});
|
|
53
65
|
|
|
54
66
|
const verdictFor = (input: ChoresReport, chore: string, repo = `app`) => {
|
|
55
67
|
const found = assessReport(input, NOW).find((verdict) => verdict.chore.id === chore && verdict.repo === repo);
|
|
@@ -70,7 +82,9 @@ describe(`what "we have not measured this" means`, () => {
|
|
|
70
82
|
// does not have is not evidence of anything, and it carries the tool's own reason rather than an invented one.
|
|
71
83
|
test(`a probe the repository cannot run says so, and never badges`, () => {
|
|
72
84
|
const input = report({
|
|
73
|
-
repos: [
|
|
85
|
+
repos: [
|
|
86
|
+
{ repo: `app`, probes: [probe({ id: `knip`, state: `unavailable`, reason: `knip is not a devDependency` })], signals: signals() },
|
|
87
|
+
],
|
|
74
88
|
});
|
|
75
89
|
const verdict = verdictFor(input, `dead-code`);
|
|
76
90
|
expect(verdict.state).toBe(`unavailable`);
|
|
@@ -107,7 +121,10 @@ describe(`the ledger debounces; it cannot hide`, () => {
|
|
|
107
121
|
// The point of digesting evidence rather than stamping a time: a fix landing, or a NEW advisory arriving,
|
|
108
122
|
// both move the evidence and both deserve to be heard again.
|
|
109
123
|
test(`evidence that has moved since the run is unsettled again`, () => {
|
|
110
|
-
const moved = report({
|
|
124
|
+
const moved = report({
|
|
125
|
+
repos: [{ repo: `app`, probes: [auditProbe([`left-pad`, `minimist`])], signals: signals() }],
|
|
126
|
+
ledger: [ledgerEntry()],
|
|
127
|
+
});
|
|
111
128
|
const verdict = verdictFor(moved, `security-advisories`);
|
|
112
129
|
expect(verdict.state).toBe(`due`);
|
|
113
130
|
expect(verdict.settled).toBe(false);
|
|
@@ -143,6 +160,76 @@ describe(`the ledger debounces; it cannot hide`, () => {
|
|
|
143
160
|
});
|
|
144
161
|
});
|
|
145
162
|
|
|
163
|
+
/* The distinction the digest alone cannot draw, and the one whose absence had the panel quoting a six-day-old
|
|
164
|
+
* dead-code count an hour after the turn that deleted it. An unchanged digest is what a probe that never re-ran
|
|
165
|
+
* produces, so "the fix did not move the numbers" and "we have not looked since the fix" reached the same branch
|
|
166
|
+
* and both came out as a confident `due`. */
|
|
167
|
+
describe(`a measurement older than the work is not evidence about the work`, () => {
|
|
168
|
+
const withAdvisories = report({ repos: [{ repo: `app`, probes: [auditProbe([`left-pad`])], signals: signals() }] });
|
|
169
|
+
const ledgerEntry = (over: Partial<ChoreLedgerEntry> = {}): ChoreLedgerEntry => ({
|
|
170
|
+
repo: `app`,
|
|
171
|
+
chore: `security-advisories`,
|
|
172
|
+
ranAt: NOW - DAY,
|
|
173
|
+
runId: `r1`,
|
|
174
|
+
outcome: `acted`,
|
|
175
|
+
digest: verdictFor(withAdvisories, `security-advisories`).digest,
|
|
176
|
+
...over,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// The probe ran a day ago; the turn started an hour ago. Whatever it did, nothing has looked since.
|
|
180
|
+
test(`a turn that landed after the measurement steps the chore down from due`, () => {
|
|
181
|
+
const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })] }, `security-advisories`);
|
|
182
|
+
expect(verdict.state).toBe(`stale`);
|
|
183
|
+
// The evidence stays on the row — it is what the reader checks the claim against — and the claim comes off.
|
|
184
|
+
expect(verdict.detail).not.toEqual([]);
|
|
185
|
+
expect(verdict.settled).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// No prompt, so the row cannot offer to spend a second turn on a finding nobody has re-checked; and no badge,
|
|
189
|
+
// which is what stops the tile lighting for work that is already done.
|
|
190
|
+
test(`a stale chore offers no turn and never badges`, () => {
|
|
191
|
+
const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })] }, `security-advisories`);
|
|
192
|
+
expect(verdict.prompt).toBeUndefined();
|
|
193
|
+
expect(unseenVerdicts([verdict], {})).toEqual([]);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Re-measuring is the whole cure: the same run, against evidence taken after it, is settled rather than stale.
|
|
197
|
+
test(`re-measuring after the turn restores the verdict — due, and now genuinely settled`, () => {
|
|
198
|
+
const remeasured = report({
|
|
199
|
+
repos: [{ repo: `app`, probes: [{ ...auditProbe([`left-pad`]), ranAt: NOW - 60_000 }], signals: signals() }],
|
|
200
|
+
ledger: [ledgerEntry({ ranAt: NOW - 3_600_000 })],
|
|
201
|
+
});
|
|
202
|
+
const verdict = verdictFor(remeasured, `security-advisories`);
|
|
203
|
+
expect(verdict.state).toBe(`due`);
|
|
204
|
+
expect(verdict.settled).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// The agent's own "these were false positives" is a judgement about the live tree, made after the measurement.
|
|
208
|
+
// It outranks staleness, or every clean run would be re-opened by the very probe age it was reported against.
|
|
209
|
+
test(`an agent reporting the findings did not hold up still clears the chore`, () => {
|
|
210
|
+
const verdict = verdictFor({ ...withAdvisories, ledger: [ledgerEntry({ ranAt: NOW - 3_600_000, outcome: `clean` })] }, `security-advisories`);
|
|
211
|
+
expect(verdict.state).toBe(`clear`);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// A snooze is the owner speaking, and it outranks both.
|
|
215
|
+
test(`a snooze still wins`, () => {
|
|
216
|
+
const verdict = verdictFor(
|
|
217
|
+
{ ...withAdvisories, ledger: [ledgerEntry({ ranAt: NOW - 3_600_000, snoozedUntil: NOW + DAY })] },
|
|
218
|
+
`security-advisories`,
|
|
219
|
+
);
|
|
220
|
+
expect(verdict.state).toBe(`snoozed`);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Every measured row carries when it was taken, so no row can pass off a week-old count as this morning's.
|
|
224
|
+
test(`every measured verdict says when it was measured, and the unmeasurable ones say nothing`, () => {
|
|
225
|
+
expect(verdictFor(withAdvisories, `security-advisories`).measuredAt).toBe(NOW - DAY);
|
|
226
|
+
// A survey rests on no measurement — it is decided by the calendar, and has nothing to be out of date.
|
|
227
|
+
expect(verdictFor(report(), `standardize-patterns`).measuredAt).toBeUndefined();
|
|
228
|
+
// Nor does a probe that never ran.
|
|
229
|
+
expect(verdictFor(report(), `security-advisories`).measuredAt).toBeUndefined();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
146
233
|
describe(`surveys are due because time passed, and say so`, () => {
|
|
147
234
|
const surveyLedger = (ranAt: number): ChoreLedgerEntry => ({
|
|
148
235
|
repo: `app`,
|
|
@@ -178,7 +265,10 @@ describe(`the badge speaks about transitions, not about statistics`, () => {
|
|
|
178
265
|
const seen = { [ledgerKey(`app`, `security-advisories`)]: first.digest };
|
|
179
266
|
expect(unseenVerdicts([first], seen)).toEqual([]);
|
|
180
267
|
|
|
181
|
-
const next = verdictFor(
|
|
268
|
+
const next = verdictFor(
|
|
269
|
+
report({ repos: [{ repo: `app`, probes: [auditProbe([`left-pad`, `tar`])], signals: signals() }] }),
|
|
270
|
+
`security-advisories`,
|
|
271
|
+
);
|
|
182
272
|
expect(unseenVerdicts([next], seen)).toHaveLength(1);
|
|
183
273
|
});
|
|
184
274
|
|
|
@@ -202,7 +292,15 @@ describe(`the badge speaks about transitions, not about statistics`, () => {
|
|
|
202
292
|
|
|
203
293
|
describe(`the findings themselves`, () => {
|
|
204
294
|
test(`complexity reports only files that are load-bearing or out of proportion, never just the ranking's top`, () => {
|
|
205
|
-
const hotspot = (path: string, complexity: number) => ({
|
|
295
|
+
const hotspot = (path: string, complexity: number) => ({
|
|
296
|
+
path,
|
|
297
|
+
commits: 20,
|
|
298
|
+
adds: 100,
|
|
299
|
+
dels: 50,
|
|
300
|
+
complexity,
|
|
301
|
+
score: complexity * 20,
|
|
302
|
+
latestMs: NOW,
|
|
303
|
+
});
|
|
206
304
|
// An even ranking has no outlier and no key module in it: a healthy repository, and an empty finding.
|
|
207
305
|
const even = report({
|
|
208
306
|
repos: [{ repo: `app`, probes: [], signals: signals({ hotspots: [hotspot(`a.ts`, 30), hotspot(`b.ts`, 28), hotspot(`c.ts`, 26)] }) }],
|
|
@@ -224,7 +322,10 @@ describe(`the findings themselves`, () => {
|
|
|
224
322
|
{
|
|
225
323
|
repo: `app`,
|
|
226
324
|
probes: [],
|
|
227
|
-
signals: signals({
|
|
325
|
+
signals: signals({
|
|
326
|
+
indexed: false,
|
|
327
|
+
hotspots: [{ path: `a.ts`, commits: 9, adds: 1, dels: 1, complexity: 900, score: 8100, latestMs: NOW }],
|
|
328
|
+
}),
|
|
228
329
|
},
|
|
229
330
|
],
|
|
230
331
|
});
|
|
@@ -259,7 +360,8 @@ describe(`the findings themselves`, () => {
|
|
|
259
360
|
});
|
|
260
361
|
|
|
261
362
|
describe(`the prompts`, () => {
|
|
262
|
-
const dueVerdict = () =>
|
|
363
|
+
const dueVerdict = () =>
|
|
364
|
+
verdictFor(report({ repos: [{ repo: `app`, probes: [auditProbe([`left-pad`])], signals: signals() }] }), `security-advisories`);
|
|
263
365
|
|
|
264
366
|
/* A prompt that counts without NAMING sends the agent off to re-derive a list we are already holding — slowly,
|
|
265
367
|
* and against a tree that has moved since. Every measured chore names its artefacts. */
|
|
@@ -283,9 +385,27 @@ describe(`the prompts`, () => {
|
|
|
283
385
|
repo: `app`,
|
|
284
386
|
probes: [
|
|
285
387
|
auditProbe([`left-pad`]),
|
|
286
|
-
probe({
|
|
287
|
-
|
|
288
|
-
|
|
388
|
+
probe({
|
|
389
|
+
id: `outdated`,
|
|
390
|
+
facts: {
|
|
391
|
+
id: `outdated`,
|
|
392
|
+
packages: [{ name: `vue`, current: `1.0.0`, latest: `2.0.0`, kind: `major`, section: `dependencies` }],
|
|
393
|
+
},
|
|
394
|
+
}),
|
|
395
|
+
probe({
|
|
396
|
+
id: `knip`,
|
|
397
|
+
facts: {
|
|
398
|
+
id: `knip`,
|
|
399
|
+
deadCode: { files: 3, exports: 2, types: 0, dependencies: 1, devDependencies: 0, sample: [`a.ts`] },
|
|
400
|
+
},
|
|
401
|
+
}),
|
|
402
|
+
probe({
|
|
403
|
+
id: `jscpd`,
|
|
404
|
+
facts: {
|
|
405
|
+
id: `jscpd`,
|
|
406
|
+
duplication: { percentage: 9, clones: 4, top: [{ lines: 20, first: `a.ts`, second: `b.ts` }] },
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
289
409
|
],
|
|
290
410
|
signals: signals({ packages: [pkg({ documented: false, dependencies: [`zod`, `joi`] })] }),
|
|
291
411
|
},
|
|
@@ -323,7 +443,7 @@ describe(`what does not apply here`, () => {
|
|
|
323
443
|
test(`a repository with no documents is not asked to re-read its documentation`, () => {
|
|
324
444
|
const verdict = verdictFor(withShape({ docs: [] }), `documentation-drift`);
|
|
325
445
|
expect(verdict.state).toBe(`not-applicable`);
|
|
326
|
-
expect(verdict.headline).toBe(`
|
|
446
|
+
expect(verdict.headline).toBe(`no architecture documents`);
|
|
327
447
|
expect(verdict.prompt).toBeUndefined();
|
|
328
448
|
});
|
|
329
449
|
|
|
@@ -352,10 +472,12 @@ describe(`what does not apply here`, () => {
|
|
|
352
472
|
// fires forever in repositories where its subject does not exist. This is the regression that motivated
|
|
353
473
|
// making `applies` a required field on SurveySpec rather than an optional one.
|
|
354
474
|
test(`a tiny repository is not surveyed for cross-cutting patterns it cannot have`, () => {
|
|
355
|
-
const tiny = report({
|
|
475
|
+
const tiny = report({
|
|
476
|
+
repos: [{ repo: `app`, probes: [], signals: signals({ totals: { files: 4, symbols: 10, complexity: 5, hotspots: 0 } }) }],
|
|
477
|
+
});
|
|
356
478
|
const verdict = verdictFor(tiny, `standardize-patterns`);
|
|
357
479
|
expect(verdict.state).toBe(`not-applicable`);
|
|
358
|
-
expect(verdict.headline).
|
|
480
|
+
expect(verdict.headline).toBe(`only 4 indexed files`);
|
|
359
481
|
});
|
|
360
482
|
|
|
361
483
|
test(`applicability is decided before measurement, so a missing probe never masks it`, () => {
|
|
@@ -373,6 +495,222 @@ describe(`what does not apply here`, () => {
|
|
|
373
495
|
test(`a fully-equipped repository rules nothing out`, () => {
|
|
374
496
|
expect(assessReport(withShape({}), NOW).filter((verdict) => verdict.state === `not-applicable`)).toEqual([]);
|
|
375
497
|
});
|
|
498
|
+
|
|
499
|
+
/* THE CAUSES HAVE TO GROUP, and that is a fact about the STRINGS rather than about the gates. The scope strip
|
|
500
|
+
* prints one line per distinct cause with the chores it costs listed beside it, so two gates that both mean
|
|
501
|
+
* "there is no package.json here" and say it in different words print two lines — and a workspace root, where
|
|
502
|
+
* a dozen chores are ruled out by three facts, is back to the paragraph-per-chore wall this phrasing replaced.
|
|
503
|
+
* Bounded rather than enumerated: a new gate may invent a new cause, it may not invent a new sentence. */
|
|
504
|
+
test(`applicability reasons are bare causes, so the ones that mean the same thing group`, () => {
|
|
505
|
+
const bare = withShape({ packageManifest: false, lockfile: false, docs: [], ci: [], dockerfiles: [], deps: [] });
|
|
506
|
+
const causes = assessReport(bare, NOW)
|
|
507
|
+
.filter((verdict) => verdict.state === `not-applicable`)
|
|
508
|
+
.map((verdict) => verdict.headline);
|
|
509
|
+
for (const cause of causes) {
|
|
510
|
+
expect(cause, cause).toMatch(/^[a-z]/);
|
|
511
|
+
expect(cause.split(` `).length, cause).toBeLessThanOrEqual(4);
|
|
512
|
+
}
|
|
513
|
+
// Four chores are ruled out by ONE absent file, and the strip says so once rather than four times.
|
|
514
|
+
expect(causes.filter((cause) => cause === `no package.json`)).toHaveLength(4);
|
|
515
|
+
});
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
/* THE FRONT-END CHORES. Four chores over two probes, tested where they decide something — the share that makes a
|
|
519
|
+
* bundle a finding, the names that make two components one component, and above all the digests, because three of
|
|
520
|
+
* these four measure things that move every time anyone writes a line of markup. */
|
|
521
|
+
const uiProbe = (scan: Partial<UiScan> = {}): ProbeResult =>
|
|
522
|
+
probe({ id: `ui`, facts: { id: `ui`, scan: { components: [], bypasses: [], idioms: [], ...scan } } });
|
|
523
|
+
|
|
524
|
+
const bundleProbe = (assets: Bundle["assets"]): ProbeResult =>
|
|
525
|
+
probe({
|
|
526
|
+
id: `bundle`,
|
|
527
|
+
facts: {
|
|
528
|
+
id: `bundle`,
|
|
529
|
+
bundle: {
|
|
530
|
+
dir: `dist`,
|
|
531
|
+
assets,
|
|
532
|
+
totalBytes: assets.reduce((sum, asset) => sum + asset.bytes, 0),
|
|
533
|
+
totalGzip: assets.reduce((sum, asset) => sum + asset.gzip, 0),
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const jscpdProbe = (top: Duplication["top"]): ProbeResult =>
|
|
539
|
+
probe({ id: `jscpd`, facts: { id: `jscpd`, duplication: { percentage: 1, clones: top.length, top } } });
|
|
540
|
+
|
|
541
|
+
const withProbes = (probes: readonly ProbeResult[], over: Partial<ChoreShape> = {}): ChoresReport =>
|
|
542
|
+
report({ repos: [{ repo: `app`, probes: [...probes], signals: signals({ shape: shape(over) }) }] });
|
|
543
|
+
|
|
544
|
+
const chunk = (path: string, gzip: number): Bundle["assets"][number] => ({ path, bytes: gzip * 3, gzip });
|
|
545
|
+
|
|
546
|
+
describe(`what the browser downloads`, () => {
|
|
547
|
+
test(`one chunk over half the transfer is the finding, and the headline names it`, () => {
|
|
548
|
+
const verdict = verdictFor(
|
|
549
|
+
withProbes([bundleProbe([chunk(`dist/vendor-DlAUqK2U.js`, 800), chunk(`dist/index-a1.js`, 100), chunk(`dist/s-b2.css`, 50)])]),
|
|
550
|
+
`bundle-weight`,
|
|
551
|
+
);
|
|
552
|
+
expect(verdict.state).toBe(`due`);
|
|
553
|
+
expect(verdict.headline).toContain(`dist/vendor-DlAUqK2U.js`);
|
|
554
|
+
expect(verdict.headline).toContain(`84%`);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
test(`a build that is actually split says nothing`, () => {
|
|
558
|
+
const even = [chunk(`dist/a-1.js`, 100), chunk(`dist/b-2.js`, 100), chunk(`dist/c-3.js`, 100), chunk(`dist/d-4.js`, 100)];
|
|
559
|
+
expect(verdictFor(withProbes([bundleProbe(even)]), `bundle-weight`).state).toBe(`clear`);
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Two files cannot tell you how a build is divided, and the larger of them is over half by arithmetic.
|
|
563
|
+
test(`too few assets to have a shape is clear, not due`, () => {
|
|
564
|
+
expect(verdictFor(withProbes([bundleProbe([chunk(`dist/a-1.js`, 900), chunk(`dist/b-2.js`, 10)])]), `bundle-weight`).state).toBe(`clear`);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
/* THE CASE THIS CHORE WOULD OTHERWISE FAIL EVERY DAY. A content hash changing is what a content hash is for,
|
|
568
|
+
* so rebuilding identical code renames every asset. Digesting the raw paths would mint new evidence on every
|
|
569
|
+
* `pnpm build` and badge forever while reporting nothing new. */
|
|
570
|
+
test(`rebuilding the same code does not read as new evidence`, () => {
|
|
571
|
+
const before = verdictFor(
|
|
572
|
+
withProbes([bundleProbe([chunk(`dist/vendor-DlAUqK2U.js`, 800), chunk(`dist/index-a1b2c3d4.js`, 100), chunk(`dist/s.css`, 50)])]),
|
|
573
|
+
`bundle-weight`,
|
|
574
|
+
);
|
|
575
|
+
const after = verdictFor(
|
|
576
|
+
withProbes([bundleProbe([chunk(`dist/vendor-Zq99XxYw.js`, 802), chunk(`dist/index-9z8y7x6w.js`, 101), chunk(`dist/s.css`, 50)])]),
|
|
577
|
+
`bundle-weight`,
|
|
578
|
+
);
|
|
579
|
+
expect(after.digest).toBe(before.digest);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test(`a genuinely different chunk appearing does`, () => {
|
|
583
|
+
const before = verdictFor(
|
|
584
|
+
withProbes([bundleProbe([chunk(`dist/vendor-DlAUqK2U.js`, 800), chunk(`dist/index-a1b2c3d4.js`, 100), chunk(`dist/s.css`, 50)])]),
|
|
585
|
+
`bundle-weight`,
|
|
586
|
+
);
|
|
587
|
+
const after = verdictFor(
|
|
588
|
+
withProbes([bundleProbe([chunk(`dist/vendor-DlAUqK2U.js`, 800), chunk(`dist/charting-a1b2c3d4.js`, 100), chunk(`dist/s.css`, 50)])]),
|
|
589
|
+
`bundle-weight`,
|
|
590
|
+
);
|
|
591
|
+
expect(after.digest).not.toBe(before.digest);
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe(`idioms the framework has replaced`, () => {
|
|
596
|
+
const idioms = (id: string, count: number) => ({ id, files: Array.from({ length: count }, (_, index) => `src/C${index}.vue`) });
|
|
597
|
+
|
|
598
|
+
test(`names what is still in use and what replaced it`, () => {
|
|
599
|
+
const verdict = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 3)] })]), `framework-idiom`);
|
|
600
|
+
expect(verdict.state).toBe(`due`);
|
|
601
|
+
expect(verdict.detail).toEqual([`3 files · the Options API → <script setup> with the Composition API`]);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
/* A migration in progress is a set that changes on every commit, so digesting the file identities — which is
|
|
605
|
+
* right for the documentation chore, whose set is packages — would badge continuously through exactly the
|
|
606
|
+
* period someone is doing the work. The bucketed count moves on real progress and not on daily churn. */
|
|
607
|
+
test(`one more file in a large migration is not news`, () => {
|
|
608
|
+
const before = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 40)] })]), `framework-idiom`);
|
|
609
|
+
const after = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 41)] })]), `framework-idiom`);
|
|
610
|
+
expect(after.digest).toBe(before.digest);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test(`a kind of legacy code the repository did not have before is`, () => {
|
|
614
|
+
const before = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 40)] })]), `framework-idiom`);
|
|
615
|
+
const after = verdictFor(withProbes([uiProbe({ idioms: [idioms(`vue-options-api`, 40), idioms(`vue-global-api`, 1)] })]), `framework-idiom`);
|
|
616
|
+
expect(after.digest).not.toBe(before.digest);
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// The daemon composes the sweep from its own copy of the table, so an image ahead of the browser can report a
|
|
620
|
+
// rule this build has no label or replacement for. A row about it could say nothing useful.
|
|
621
|
+
test(`an idiom this build of the book does not know is dropped, not shown unnamed`, () => {
|
|
622
|
+
expect(verdictFor(withProbes([uiProbe({ idioms: [idioms(`react-from-2029`, 5)] })]), `framework-idiom`).state).toBe(`clear`);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
/* A probe's command is a fixed string, so every rule sweeps every repository and the Angular patterns get
|
|
626
|
+
* their chance in a Vue codebase. Run against this workspace it found one file: the rule table itself, which
|
|
627
|
+
* quotes `RouterModule.forRoot` as a pattern. What the repository declares is what settles it. */
|
|
628
|
+
test(`an idiom from a framework the repository does not declare is not its problem`, () => {
|
|
629
|
+
const vue = withProbes([uiProbe({ idioms: [idioms(`angular-ngmodule`, 4)] })], { deps: [`vue`] });
|
|
630
|
+
const angular = withProbes([uiProbe({ idioms: [idioms(`angular-ngmodule`, 4)] })], { deps: [`@angular/core`] });
|
|
631
|
+
expect(verdictFor(vue, `framework-idiom`).state).toBe(`clear`);
|
|
632
|
+
expect(verdictFor(angular, `framework-idiom`).state).toBe(`due`);
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
describe(`components built twice`, () => {
|
|
637
|
+
const components = (...paths: readonly string[]) => uiProbe({ components: [...paths] });
|
|
638
|
+
|
|
639
|
+
test(`a shared name across two directories is a family, whatever each file is called`, () => {
|
|
640
|
+
const verdict = verdictFor(
|
|
641
|
+
withProbes([components(`src/ui/BaseButton.vue`, `src/checkout/ButtonV2.tsx`), jscpdProbe([])]),
|
|
642
|
+
`component-overlap`,
|
|
643
|
+
);
|
|
644
|
+
expect(verdict.state).toBe(`due`);
|
|
645
|
+
expect(verdict.headline).toBe(`1 name used by more than one component`);
|
|
646
|
+
expect(verdict.detail).toEqual([`button · src/checkout/ButtonV2.tsx, src/ui/BaseButton.vue`]);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
test(`components that merely coexist are not a finding`, () => {
|
|
650
|
+
expect(verdictFor(withProbes([components(`src/Button.vue`, `src/Card.vue`), jscpdProbe([])]), `component-overlap`).state).toBe(`clear`);
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// The other half of the evidence: two components with unrelated names doing the same work, which no name
|
|
654
|
+
// comparison can reach and jscpd already measured.
|
|
655
|
+
test(`a clone counts only when a component sits on both sides of it`, () => {
|
|
656
|
+
const ui = components(`src/Chart.vue`, `src/Graph.vue`);
|
|
657
|
+
const shared = verdictFor(
|
|
658
|
+
withProbes([ui, jscpdProbe([{ lines: 40, first: `./src/Chart.vue`, second: `./src/Graph.vue` }])]),
|
|
659
|
+
`component-overlap`,
|
|
660
|
+
);
|
|
661
|
+
const oneSided = verdictFor(
|
|
662
|
+
withProbes([ui, jscpdProbe([{ lines: 40, first: `./src/Chart.vue`, second: `./src/utils/format.ts` }])]),
|
|
663
|
+
`component-overlap`,
|
|
664
|
+
);
|
|
665
|
+
expect(shared.state).toBe(`due`);
|
|
666
|
+
expect(shared.headline).toBe(`1 clone spanning two of them`);
|
|
667
|
+
expect(oneSided.state).toBe(`clear`);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
/* Half a measurement would let the row claim it looked for shared logic in a repository where jscpd has never
|
|
671
|
+
* run — the "measured and found nothing" lie the unavailable state exists to prevent. */
|
|
672
|
+
test(`without the clone sweep the chore is unavailable, not clear`, () => {
|
|
673
|
+
expect(verdictFor(withProbes([components(`src/Button.vue`, `src/ui/Button.vue`)]), `component-overlap`).state).toBe(`unavailable`);
|
|
674
|
+
});
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
describe(`hard-coded styles`, () => {
|
|
678
|
+
test(`counts the values and leads with the heaviest files`, () => {
|
|
679
|
+
const verdict = verdictFor(
|
|
680
|
+
withProbes([
|
|
681
|
+
uiProbe({
|
|
682
|
+
bypasses: [
|
|
683
|
+
{ path: `src/Checkout.vue`, count: 11 },
|
|
684
|
+
{ path: `src/Nav.vue`, count: 2 },
|
|
685
|
+
],
|
|
686
|
+
}),
|
|
687
|
+
]),
|
|
688
|
+
`tailwind-arbitrary-values`,
|
|
689
|
+
);
|
|
690
|
+
expect(verdict.state).toBe(`due`);
|
|
691
|
+
expect(verdict.headline).toBe(`13 hard-coded values across 2 files`);
|
|
692
|
+
expect(verdict.detail[0]).toBe(`src/Checkout.vue · 11 values`);
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Tailwind gates this one alone — a Vue repository with no Tailwind has no theme scale to have bypassed, and
|
|
696
|
+
// a row saying so would be the surface inventing a subject.
|
|
697
|
+
test(`a repository without Tailwind is not asked the question at all`, () => {
|
|
698
|
+
const verdict = verdictFor(
|
|
699
|
+
withProbes([uiProbe({ bypasses: [{ path: `src/Nav.vue`, count: 2 }] })], { deps: [`vue`] }),
|
|
700
|
+
`tailwind-arbitrary-values`,
|
|
701
|
+
);
|
|
702
|
+
expect(verdict.state).toBe(`not-applicable`);
|
|
703
|
+
expect(verdict.headline).toBe(`no Tailwind`);
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
// And the framework gate the other four share, from the other side: deps come from shape, not from packages,
|
|
707
|
+
// because a single-package Vite app has no workspace packages at all.
|
|
708
|
+
test(`a repository with no framework rules the front-end chores out entirely`, () => {
|
|
709
|
+
const states = [`bundle-weight`, `framework-idiom`, `component-overlap`, `tailwind-arbitrary-values`].map(
|
|
710
|
+
(id) => verdictFor(withProbes([], { deps: [`pino`] }), id).state,
|
|
711
|
+
);
|
|
712
|
+
expect(states).toEqual([`not-applicable`, `not-applicable`, `not-applicable`, `not-applicable`]);
|
|
713
|
+
});
|
|
376
714
|
});
|
|
377
715
|
|
|
378
716
|
/* THE CRITERION — the rule in words, next to the evidence that met it. A row that reports a number without the
|