superdev-cli 0.1.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/.claude-plugin/marketplace.json +33 -0
- package/.claude-plugin/plugin.json +21 -0
- package/.codex-plugin/plugin.json +27 -0
- package/CODE_OF_CONDUCT.md +109 -0
- package/CONTRIBUTING.md +205 -0
- package/LICENSE +202 -0
- package/NOTICE +11 -0
- package/README.md +1051 -0
- package/SECURITY.md +135 -0
- package/THIRD-PARTY-NOTICES.md +253 -0
- package/hooks/hooks.json +61 -0
- package/hooks/run.mjs +88 -0
- package/package.json +65 -0
- package/references/confidentiality.md +48 -0
- package/references/evidence-and-risk.md +73 -0
- package/references/operating-model.md +105 -0
- package/references/platform-capabilities.md +51 -0
- package/references/project-record.md +91 -0
- package/references/provider-contracts.md +102 -0
- package/scripts/doctor/doctor.mjs +539 -0
- package/scripts/privacy/scan-history.mjs +163 -0
- package/scripts/privacy/scan.mjs +368 -0
- package/scripts/validate/README.md +94 -0
- package/scripts/validate/common.mjs +149 -0
- package/scripts/validate/data-model.mjs +225 -0
- package/scripts/validate/dependencies.mjs +69 -0
- package/scripts/validate/docs-templates.mjs +99 -0
- package/scripts/validate/footprint.mjs +78 -0
- package/scripts/validate/imports.mjs +61 -0
- package/scripts/validate/manifests.mjs +173 -0
- package/scripts/validate/markdown.mjs +123 -0
- package/scripts/validate/migrations.mjs +187 -0
- package/scripts/validate/no-tests.mjs +136 -0
- package/scripts/validate/privacy.mjs +115 -0
- package/scripts/validate/record-links.mjs +127 -0
- package/scripts/validate/skill-commands.mjs +190 -0
- package/scripts/validate/skills.mjs +112 -0
- package/scripts/validate/specification.mjs +88 -0
- package/scripts/validate/style.mjs +78 -0
- package/scripts/validate/validate-all.mjs +175 -0
- package/skills/debug/SKILL.md +68 -0
- package/skills/decision/SKILL.md +99 -0
- package/skills/docs/SKILL.md +86 -0
- package/skills/docs/assets/fragments/api/events.md +14 -0
- package/skills/docs/assets/fragments/api/graphql.md +14 -0
- package/skills/docs/assets/fragments/api/local-only.md +13 -0
- package/skills/docs/assets/fragments/api/rest.md +13 -0
- package/skills/docs/assets/fragments/api/rpc.md +13 -0
- package/skills/docs/assets/fragments/async/event-bus.md +13 -0
- package/skills/docs/assets/fragments/async/none.md +11 -0
- package/skills/docs/assets/fragments/async/platform-jobs.md +13 -0
- package/skills/docs/assets/fragments/async/queue.md +13 -0
- package/skills/docs/assets/fragments/async/scheduler.md +13 -0
- package/skills/docs/assets/fragments/auth/detected-provider.md +15 -0
- package/skills/docs/assets/fragments/auth/neutral-base.md +14 -0
- package/skills/docs/assets/fragments/data/document.md +13 -0
- package/skills/docs/assets/fragments/data/external-saas.md +13 -0
- package/skills/docs/assets/fragments/data/key-value.md +13 -0
- package/skills/docs/assets/fragments/data/none.md +12 -0
- package/skills/docs/assets/fragments/data/sql-orm.md +13 -0
- package/skills/docs/assets/fragments/data/sql-plain.md +13 -0
- package/skills/docs/assets/fragments/env/conventional.md +13 -0
- package/skills/docs/assets/fragments/env/envx.md +13 -0
- package/skills/docs/assets/fragments/env/managed-platform.md +13 -0
- package/skills/docs/assets/fragments/env/none.md +11 -0
- package/skills/docs/assets/fragments/ui/api-only.md +13 -0
- package/skills/docs/assets/fragments/ui/cli.md +14 -0
- package/skills/docs/assets/fragments/ui/desktop.md +14 -0
- package/skills/docs/assets/fragments/ui/mobile.md +14 -0
- package/skills/docs/assets/fragments/ui/web.md +14 -0
- package/skills/docs/assets/templates/adr.md +62 -0
- package/skills/docs/assets/templates/api.md +30 -0
- package/skills/docs/assets/templates/architecture.md +52 -0
- package/skills/docs/assets/templates/change-impact-drift-report.md +29 -0
- package/skills/docs/assets/templates/compliance.md +29 -0
- package/skills/docs/assets/templates/data-schema.md +43 -0
- package/skills/docs/assets/templates/feature.md +43 -0
- package/skills/docs/assets/templates/foundations.md +55 -0
- package/skills/docs/assets/templates/jobs-webhooks.md +36 -0
- package/skills/docs/assets/templates/module-inventory.md +19 -0
- package/skills/docs/assets/templates/module.md +50 -0
- package/skills/docs/assets/templates/nfr.md +22 -0
- package/skills/docs/assets/templates/observability.md +28 -0
- package/skills/docs/assets/templates/pages-ui-actions.md +47 -0
- package/skills/docs/assets/templates/project-summary.md +44 -0
- package/skills/docs/assets/templates/roles-permissions.md +26 -0
- package/skills/docs/assets/templates/test-plan.md +23 -0
- package/skills/docs/assets/templates/workflow-state-machine.md +43 -0
- package/skills/docs/references/adr-authoring.md +24 -0
- package/skills/docs/references/apis-and-data.md +23 -0
- package/skills/docs/references/capability-fragments.md +25 -0
- package/skills/docs/references/change-tracking.md +62 -0
- package/skills/docs/references/diagrams.md +31 -0
- package/skills/docs/references/discovery.md +59 -0
- package/skills/docs/references/edge-cases.md +45 -0
- package/skills/docs/references/foundations-modules.md +18 -0
- package/skills/docs/references/ingestion.md +52 -0
- package/skills/docs/references/initialize-adopt.md +25 -0
- package/skills/docs/references/module-decomposition.md +38 -0
- package/skills/docs/references/profiles.md +60 -0
- package/skills/docs/references/quality-attributes.md +27 -0
- package/skills/docs/references/reverse-engineer.md +21 -0
- package/skills/docs/references/spec-depths.md +31 -0
- package/skills/docs/references/summarize.md +18 -0
- package/skills/docs/references/surfaces-and-actions.md +24 -0
- package/skills/docs/references/validation.md +47 -0
- package/skills/docs/references/workflows-and-jobs.md +25 -0
- package/skills/docs/scripts/ingest.mjs +984 -0
- package/skills/docs/scripts/profile-detect.mjs +299 -0
- package/skills/docs/scripts/screen.mjs +96 -0
- package/skills/docs/scripts/template-lint.mjs +143 -0
- package/skills/docs/scripts/validate-docs.mjs +363 -0
- package/skills/doctor/SKILL.md +167 -0
- package/skills/feature/SKILL.md +132 -0
- package/skills/init/SKILL.md +137 -0
- package/skills/project/SKILL.md +261 -0
- package/skills/project/references/commands.md +213 -0
- package/skills/resume/SKILL.md +79 -0
- package/skills/review/SKILL.md +91 -0
- package/skills/status/SKILL.md +85 -0
- package/skills/task/SKILL.md +183 -0
- package/src/cli/product-map.mjs +468 -0
- package/src/cli/render.mjs +544 -0
- package/src/cli.mjs +2774 -0
- package/src/cloud/crypto.mjs +118 -0
- package/src/cloud/merge.mjs +186 -0
- package/src/cloud/policy.mjs +115 -0
- package/src/cloud/sync.mjs +512 -0
- package/src/cloud/transport.mjs +116 -0
- package/src/db/connect.mjs +189 -0
- package/src/db/maintenance.mjs +419 -0
- package/src/db/migrate.mjs +188 -0
- package/src/db/migrations/001_initial.sql +1024 -0
- package/src/db/migrations/002_docs_coverage.sql +126 -0
- package/src/db/migrations/003_memory_retrieval_and_integrity.sql +168 -0
- package/src/db/migrations/004_task_categories.sql +59 -0
- package/src/db/migrations/005_executable_evidence.sql +32 -0
- package/src/db/migrations/006_module_and_feature_boundaries.sql +27 -0
- package/src/db/migrations/007_drop_redundant_module_users.sql +13 -0
- package/src/db/migrations/008_changes_assumptions_test_plans_api_services.sql +166 -0
- package/src/db/migrations/009_retired_documents.sql +54 -0
- package/src/db/migrations/010_test_plan_evidence.sql +20 -0
- package/src/db/migrations/011_criterion_waiver.sql +13 -0
- package/src/db/migrations/012_synchronization.sql +56 -0
- package/src/db/store.mjs +327 -0
- package/src/decisions/record.mjs +167 -0
- package/src/docs/proposals.mjs +871 -0
- package/src/docs/render.mjs +1424 -0
- package/src/docs/templates.mjs +1281 -0
- package/src/features/acceptance.mjs +315 -0
- package/src/features/specify.mjs +248 -0
- package/src/init/discovery.mjs +901 -0
- package/src/init/index.mjs +784 -0
- package/src/init/questions.mjs +562 -0
- package/src/memory/benchmark.mjs +171 -0
- package/src/memory/capture.mjs +135 -0
- package/src/memory/consolidate.mjs +255 -0
- package/src/memory/index.mjs +810 -0
- package/src/model/ids.mjs +185 -0
- package/src/model/screening.mjs +150 -0
- package/src/model/toolkit.mjs +258 -0
- package/src/model/vocabulary.mjs +190 -0
- package/src/product/assumptions.mjs +120 -0
- package/src/product/changes.mjs +180 -0
- package/src/product/test-plans.mjs +183 -0
- package/src/progress/index.mjs +1364 -0
- package/src/runtime/harness.mjs +264 -0
- package/src/runtime/hooks.mjs +1021 -0
- package/src/runtime/identity.mjs +305 -0
- package/src/runtime/resume.mjs +343 -0
- package/src/runtime/session.mjs +679 -0
- package/src/runtime/version.mjs +315 -0
- package/src/service/assets/control-center.html +206 -0
- package/src/service/assets/control-center.manifest.json +7 -0
- package/src/service/manage.mjs +542 -0
- package/src/service/mutations.mjs +860 -0
- package/src/service/read-model.mjs +1557 -0
- package/src/service/server.mjs +783 -0
- package/src/tasks/categories.mjs +154 -0
- package/src/tasks/derive.mjs +977 -0
- package/src/tasks/lifecycle.mjs +830 -0
- package/src/verify/index.mjs +236 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// Re-run the checks that evidence stands on.
|
|
2
|
+
//
|
|
3
|
+
// Completion is gated on evidence, and evidence was a sentence written once and
|
|
4
|
+
// believed forever. Nothing re-read it, so a task completed in January still
|
|
5
|
+
// claimed to be proven in June by a check that had stopped passing in March.
|
|
6
|
+
// The schema always expected better: verification_evidence has carried status,
|
|
7
|
+
// stale_at and content_hash from the first migration, and nothing ever set them.
|
|
8
|
+
//
|
|
9
|
+
// So evidence carries the command that proved it, and this re-runs them. A
|
|
10
|
+
// claim that no longer holds is marked stale and reported. Whether that reopens
|
|
11
|
+
// the task is a separate decision: reporting first, and gating once the backlog
|
|
12
|
+
// is clean, is the order that does not disrupt work in flight.
|
|
13
|
+
//
|
|
14
|
+
// Not every verification has a command. Reading a screenshot, confirming a
|
|
15
|
+
// decision with the owner, watching a migration take a backup: requiring a
|
|
16
|
+
// command for those would only encourage inventing one, which is worse than
|
|
17
|
+
// saying plainly that a check is manual.
|
|
18
|
+
|
|
19
|
+
import { execFile } from "node:child_process";
|
|
20
|
+
import { promisify } from "node:util";
|
|
21
|
+
|
|
22
|
+
import { mutate, query } from "../db/store.mjs";
|
|
23
|
+
|
|
24
|
+
const run = promisify(execFile);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What may run unattended, enumerated by exact shape.
|
|
28
|
+
*
|
|
29
|
+
* A recorded command is text that arrived from somewhere, most often from an
|
|
30
|
+
* agent, and it is executed on the machine of whoever runs verify. An earlier
|
|
31
|
+
* version of this allowed a list of programs and rejected a list of dangerous
|
|
32
|
+
* substrings. That is the wrong way round, and a security review was right to
|
|
33
|
+
* say so: a program allowlist plus a substring blocklist does not make argv
|
|
34
|
+
* safe, because the danger usually lives in a flag rather than in the program.
|
|
35
|
+
*
|
|
36
|
+
* rg --pre=/bin/sh runs an arbitrary program per file
|
|
37
|
+
* node --require=./evil.js loads a module before the script
|
|
38
|
+
* npm run <anything> runs whatever package.json says
|
|
39
|
+
* git --exec-path=/tmp relocates the helper binaries git then runs
|
|
40
|
+
*
|
|
41
|
+
* Each of those passes a program allowlist and carries no shell punctuation.
|
|
42
|
+
*
|
|
43
|
+
* So the shapes are enumerated instead. Only two are allowed, and both are
|
|
44
|
+
* things this project actually records: one of its own scripts, and grep. Every
|
|
45
|
+
* token beginning with a hyphen must appear in that program's flag allowlist,
|
|
46
|
+
* and no positional argument may look like a flag. Nothing here needs npm, git,
|
|
47
|
+
* sed or ripgrep, and breadth that is never used is only attack surface.
|
|
48
|
+
*
|
|
49
|
+
* This is a narrow gate, not a sandbox. A check that needs more than this
|
|
50
|
+
* should be run by a person who has read it.
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/** Scripts of this project that may be run, and nothing else. */
|
|
54
|
+
const SCRIPT = /^(src\/cli\.mjs|scripts\/[A-Za-z0-9_\/-]+\.mjs)$/;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Flags each program may carry.
|
|
58
|
+
*
|
|
59
|
+
* grep cannot execute anything, but its arguments are still constrained so a
|
|
60
|
+
* pattern can never be read as a flag.
|
|
61
|
+
*/
|
|
62
|
+
const FLAGS = {
|
|
63
|
+
grep: new Set(["-n", "-i", "-c", "-l", "-L", "-E", "-F", "-w", "-x", "-o", "-r", "-R", "-q", "-s", "-H", "-h", "-a"]),
|
|
64
|
+
// The script's own flags are read by the script, not by node, so they are
|
|
65
|
+
// constrained only by refusing anything that would change state.
|
|
66
|
+
node: null,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const CHANGES_STATE = /^--(apply|force|write|fix|delete|remove)\b/;
|
|
70
|
+
|
|
71
|
+
export const E = { UNSAFE: "E_UNSAFE_CHECK" };
|
|
72
|
+
|
|
73
|
+
/** Why a command will not be run here, or null when it will. */
|
|
74
|
+
export function refuseReason(command) {
|
|
75
|
+
const text = String(command ?? "").trim();
|
|
76
|
+
if (!text) return "no command recorded";
|
|
77
|
+
// Shell punctuation cannot do anything through execFile, but its presence
|
|
78
|
+
// means the command was written expecting a shell and will not behave.
|
|
79
|
+
if (/[;&|<>`$()]/.test(text)) return "written for a shell, so it cannot run without one";
|
|
80
|
+
|
|
81
|
+
const tokens = tokenize(text);
|
|
82
|
+
const [bin, ...rest] = tokens;
|
|
83
|
+
|
|
84
|
+
// A check that runs the checker is a loop, and it always reports a failure
|
|
85
|
+
// that says nothing: verify exits non-zero whenever anything could not run,
|
|
86
|
+
// so verify running verify can never pass however healthy the project is.
|
|
87
|
+
// The same shape made the integrity test plan unable to pass once it had
|
|
88
|
+
// failed once. A claim about verify has to be proven by watching it, not by
|
|
89
|
+
// asking it about itself.
|
|
90
|
+
if (/\bcli\.mjs\s+verify\b/.test(text)) {
|
|
91
|
+
return "it runs verify, which is what is running it, so the answer would only ever describe the loop";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (bin === "node") {
|
|
95
|
+
// The script path must come first. A node option before it can load
|
|
96
|
+
// arbitrary code, which is the whole risk with running node at all.
|
|
97
|
+
const [script, ...args] = rest;
|
|
98
|
+
if (!script) return "node with no script";
|
|
99
|
+
if (!SCRIPT.test(script)) return `node may only run this project's own scripts, not ${script}`;
|
|
100
|
+
for (const a of args) {
|
|
101
|
+
if (CHANGES_STATE.test(a)) return "it would change something, and a check must not";
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (bin === "grep") {
|
|
107
|
+
const allowed = FLAGS.grep;
|
|
108
|
+
let seenSeparator = false;
|
|
109
|
+
let positionals = 0;
|
|
110
|
+
for (const a of rest) {
|
|
111
|
+
if (a === "--") { seenSeparator = true; continue; }
|
|
112
|
+
if (!seenSeparator && a.startsWith("-") && a !== "-") {
|
|
113
|
+
// Combined short flags are one token and several flags. Checking the
|
|
114
|
+
// token whole refused `grep -rn pattern src`, which is safe and
|
|
115
|
+
// ordinary, so each letter is checked instead. A long flag has no
|
|
116
|
+
// letters to split and is still checked as itself.
|
|
117
|
+
const combined = /^-[A-Za-z]{2,}$/.test(a)
|
|
118
|
+
? [...a.slice(1)].map((letter) => `-${letter}`)
|
|
119
|
+
: [a];
|
|
120
|
+
for (const flag of combined) {
|
|
121
|
+
if (!allowed.has(flag)) {
|
|
122
|
+
return combined.length > 1
|
|
123
|
+
? `grep flag ${flag}, part of ${a}, is not one this will run`
|
|
124
|
+
: `grep flag ${flag} is not one this will run`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
positionals += 1;
|
|
130
|
+
}
|
|
131
|
+
if (positionals < 1) return "grep with no pattern";
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return `${bin} is not a program this will run unattended`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Split on whitespace, keeping quoted runs whole. */
|
|
139
|
+
export function tokenize(command) {
|
|
140
|
+
const out = [];
|
|
141
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
142
|
+
let m;
|
|
143
|
+
while ((m = re.exec(command))) out.push(m[1] ?? m[2] ?? m[3]);
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Run one recorded check.
|
|
149
|
+
*
|
|
150
|
+
* A non-zero exit that still produced output counts as a run that failed, not
|
|
151
|
+
* as a broken command: that is exactly the signal worth having, because a
|
|
152
|
+
* validator which starts finding things is how a claim stops being true.
|
|
153
|
+
*/
|
|
154
|
+
export async function runCheck(root, command, { timeoutMs = 120000 } = {}) {
|
|
155
|
+
const refused = refuseReason(command);
|
|
156
|
+
if (refused) return { result: "error", detail: refused };
|
|
157
|
+
|
|
158
|
+
const [bin, ...rest] = tokenize(command);
|
|
159
|
+
try {
|
|
160
|
+
const { stdout } = await run(bin, rest, { cwd: root, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 });
|
|
161
|
+
return { result: "pass", detail: String(stdout).trim().split("\n").slice(-1)[0]?.slice(0, 160) ?? "" };
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (typeof error.code === "number") {
|
|
164
|
+
const out = `${error.stdout ?? ""}${error.stderr ?? ""}`.trim();
|
|
165
|
+
return { result: "fail", detail: out.split("\n").slice(-1)[0]?.slice(0, 160) || `exit ${error.code}` };
|
|
166
|
+
}
|
|
167
|
+
return { result: "error", detail: String(error.message).slice(0, 160) };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Re-run every current piece of evidence that carries a command.
|
|
173
|
+
*
|
|
174
|
+
* Without `apply` nothing is written, which makes this safe to run at any time
|
|
175
|
+
* and is the mode worth putting in front of a person: it answers "is what we
|
|
176
|
+
* believe still true?" without changing the answer.
|
|
177
|
+
*/
|
|
178
|
+
export async function verifyEvidence(root, { apply = false, taskId = null, limit = null } = {}) {
|
|
179
|
+
const rows = await query(root, (db) => db.all(
|
|
180
|
+
`SELECT e.id, e.task_id, e.summary, e.check_command, e.result, e.last_check_result, t.status AS task_status
|
|
181
|
+
FROM verification_evidence e
|
|
182
|
+
LEFT JOIN tasks t ON t.id = e.task_id
|
|
183
|
+
WHERE e.status = 'current'
|
|
184
|
+
${taskId ? "AND e.task_id = ?" : ""}
|
|
185
|
+
ORDER BY e.id`,
|
|
186
|
+
...(taskId ? [taskId] : []),
|
|
187
|
+
));
|
|
188
|
+
|
|
189
|
+
const withCommand = rows.filter((r) => String(r.check_command ?? "").trim());
|
|
190
|
+
const manual = rows.length - withCommand.length;
|
|
191
|
+
const targets = limit ? withCommand.slice(0, limit) : withCommand;
|
|
192
|
+
|
|
193
|
+
const checked = [];
|
|
194
|
+
for (const row of targets) {
|
|
195
|
+
const outcome = await runCheck(root, row.check_command);
|
|
196
|
+
checked.push({ ...row, ...outcome });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const nowFailing = checked.filter((c) => c.result === "fail");
|
|
200
|
+
const errored = checked.filter((c) => c.result === "error");
|
|
201
|
+
|
|
202
|
+
if (apply && checked.length) {
|
|
203
|
+
const at = new Date().toISOString();
|
|
204
|
+
await mutate(root, async (db) => {
|
|
205
|
+
for (const c of checked) {
|
|
206
|
+
await db.run(
|
|
207
|
+
"UPDATE verification_evidence SET last_checked_at = ?, last_check_result = ? WHERE id = ?",
|
|
208
|
+
at, c.result, c.id,
|
|
209
|
+
);
|
|
210
|
+
// Only a check that ran and failed retracts the claim. A command that
|
|
211
|
+
// could not run says nothing about whether the product is correct, and
|
|
212
|
+
// marking evidence stale for it would turn a broken check into a false
|
|
213
|
+
// accusation against working code.
|
|
214
|
+
if (c.result === "fail") {
|
|
215
|
+
await db.run(
|
|
216
|
+
"UPDATE verification_evidence SET status = 'stale', stale_at = ? WHERE id = ?",
|
|
217
|
+
at, c.id,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
applied: apply,
|
|
226
|
+
evidenceCurrent: rows.length,
|
|
227
|
+
withCommand: withCommand.length,
|
|
228
|
+
manualOnly: manual,
|
|
229
|
+
checked: checked.length,
|
|
230
|
+
stillPassing: checked.filter((c) => c.result === "pass").length,
|
|
231
|
+
noLongerPassing: nowFailing.length,
|
|
232
|
+
couldNotRun: errored.length,
|
|
233
|
+
failing: nowFailing.map((c) => ({ evidence: c.id, task: c.task_id, taskStatus: c.task_status, command: c.check_command, detail: c.detail })),
|
|
234
|
+
unrunnable: errored.map((c) => ({ evidence: c.id, task: c.task_id, command: c.check_command, why: c.detail })),
|
|
235
|
+
};
|
|
236
|
+
}
|