@workit-dev/core 0.0.1 → 1.0.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/LICENSE +21 -0
- package/README.md +22 -43
- package/package.json +56 -6
- package/schemas/config.json +74 -0
- package/schemas/vcs.json +79 -0
- package/schemas/workspaces.json +1029 -0
- package/schemas/youtrack.json +84 -0
- package/scripts/analyze-release-scope.ts +215 -0
- package/scripts/bundled-deps.json +131 -0
- package/scripts/fixtures/sample-plan.md +15 -0
- package/scripts/fixtures/sample-sdd/progress.md +2 -0
- package/scripts/install-codex-plugin.sh +28 -0
- package/scripts/install-cursor-plugin.sh +146 -0
- package/scripts/install-opencode-plugin.sh +79 -0
- package/scripts/install-pi-package.sh +56 -0
- package/scripts/lib/config-dir.sh +28 -0
- package/scripts/publish-changed-packages.ts +395 -0
- package/scripts/rewrite-workspace-deps.ts +53 -0
- package/scripts/run-cursor-mcp.sh +6 -0
- package/scripts/sync-release-manifests.ts +82 -0
- package/scripts/sync-runtime.sh +142 -0
- package/scripts/validate-cursor-marketplace.ts +171 -0
- package/scripts/verify-bundle-sources.ts +134 -0
- package/scripts/write-schemas.ts +16 -0
- package/skills/workit-architecture/SKILL.md +72 -0
- package/skills/workit-architecture/agents/openai.yaml +3 -0
- package/skills/workit-architecture/references/instruction-files.md +52 -0
- package/skills/workit-architecture/references/vocabulary.md +53 -0
- package/skills/workit-bdd/SKILL.md +53 -0
- package/skills/workit-continue/SKILL.md +54 -0
- package/skills/workit-debug/SKILL.md +45 -0
- package/skills/workit-deslop/SKILL.md +58 -0
- package/skills/workit-fanout/SKILL.md +71 -0
- package/skills/workit-fanout/references/brief.md +141 -0
- package/skills/workit-fanout/references/orchestration.md +169 -0
- package/skills/workit-implement/SKILL.md +71 -0
- package/skills/workit-retro/SKILL.md +72 -0
- package/skills/workit-retro/agents/openai.yaml +3 -0
- package/skills/workit-retro/references/sources.md +35 -0
- package/skills/workit-retro/references/steering.md +39 -0
- package/skills/workit-review/SKILL.md +66 -0
- package/skills/workit-review/references/impact.md +24 -0
- package/skills/workit-shape/SKILL.md +71 -0
- package/skills/workit-shape/references/diagrams.md +17 -0
- package/skills/workit-shape/references/knowledge.md +61 -0
- package/skills/workit-shape/references/mockups.md +15 -0
- package/skills/workit-shape/references/slicing.md +44 -0
- package/skills/workit-ship/SKILL.md +71 -0
- package/skills/workit-ship/references/changelog.md +44 -0
- package/skills/workit-ship/references/pr-body.md +32 -0
- package/skills/workit-ship/references/release.md +39 -0
- package/skills/workit-test-audit/SKILL.md +54 -0
- package/skills/workit-verify-app/SKILL.md +68 -0
- package/skills/workit-verify-app/references/template.md +51 -0
- package/src/autonomy.ts +910 -0
- package/src/check-config.ts +613 -0
- package/src/checks.ts +534 -0
- package/src/ci-waiter.ts +159 -0
- package/src/core/boundary.ts +55 -0
- package/src/core/branch-policy.ts +112 -0
- package/src/core/branch.ts +1192 -0
- package/src/core/changelog-gate.ts +349 -0
- package/src/core/changelog-merge.ts +214 -0
- package/src/core/changelog.ts +392 -0
- package/src/core/cleanup.ts +638 -0
- package/src/core/cli-locate.ts +173 -0
- package/src/core/commit-flavors.ts +65 -0
- package/src/core/config-schemas.ts +653 -0
- package/src/core/config.ts +351 -0
- package/src/core/context-read.ts +144 -0
- package/src/core/docs-gate.ts +69 -0
- package/src/core/finding-evidence.ts +89 -0
- package/src/core/finding-queue.ts +36 -0
- package/src/core/git.ts +73 -0
- package/src/core/gitignore.ts +52 -0
- package/src/core/heavy-eta.ts +170 -0
- package/src/core/heavy-lease.ts +290 -0
- package/src/core/heavy-rules.ts +373 -0
- package/src/core/heavy-scope.ts +131 -0
- package/src/core/heavy-words.ts +83 -0
- package/src/core/heavy.ts +535 -0
- package/src/core/hygiene.ts +149 -0
- package/src/core/init.ts +218 -0
- package/src/core/live-session.ts +333 -0
- package/src/core/logger.ts +310 -0
- package/src/core/methods.ts +185 -0
- package/src/core/model-fallback.ts +208 -0
- package/src/core/node-resolve.mjs +249 -0
- package/src/core/operation-input.ts +632 -0
- package/src/core/package-root.ts +28 -0
- package/src/core/policy/attributes.ts +97 -0
- package/src/core/policy/bookkeeping.ts +119 -0
- package/src/core/policy/carry.ts +122 -0
- package/src/core/policy/check-evidence.ts +115 -0
- package/src/core/policy/current-judgment.ts +20 -0
- package/src/core/policy/derive.ts +387 -0
- package/src/core/policy/judgment.ts +246 -0
- package/src/core/policy/lane.ts +562 -0
- package/src/core/policy/pr-lane.ts +32 -0
- package/src/core/policy/substance.ts +365 -0
- package/src/core/policy-resolver.ts +94 -0
- package/src/core/pr-create.ts +448 -0
- package/src/core/pr-template.ts +162 -0
- package/src/core/release-notes.ts +197 -0
- package/src/core/release-tracks.ts +779 -0
- package/src/core/remotes.ts +436 -0
- package/src/core/repo-context.ts +542 -0
- package/src/core/repo-paths.ts +29 -0
- package/src/core/resources.ts +1299 -0
- package/src/core/role-models.ts +603 -0
- package/src/core/route-intent.ts +305 -0
- package/src/core/rules.ts +65 -0
- package/src/core/runtime-identity.ts +63 -0
- package/src/core/safe-write.ts +87 -0
- package/src/core/scope-glob.ts +56 -0
- package/src/core/scripts.ts +6 -0
- package/src/core/skill-manifests.ts +173 -0
- package/src/core/store-lock.ts +374 -0
- package/src/core/support-matrix.ts +24 -0
- package/src/core/task-context.ts +247 -0
- package/src/core/task-contract.ts +1279 -0
- package/src/core/task-delivery.ts +109 -0
- package/src/core/task-engine.ts +2248 -0
- package/src/core/task-evaluation.ts +1147 -0
- package/src/core/task-store.ts +2649 -0
- package/src/core/task-view.ts +180 -0
- package/src/core/templates.ts +22 -0
- package/src/core/tracker-issues.ts +174 -0
- package/src/core/vcs-config.ts +540 -0
- package/src/core/workers.ts +62 -0
- package/src/core/workit-paths.mjs +242 -0
- package/src/core/workspace-branch-policy.ts +123 -0
- package/src/core/workspaces.ts +1745 -0
- package/src/core/youtrack-backend.ts +288 -0
- package/src/core/youtrack-tools.ts +260 -0
- package/src/core/youtrack-writes.ts +73 -0
- package/src/core/youtrack-ytrack.ts +217 -0
- package/src/core/youtrack.ts +454 -0
- package/src/core/ytrack-skill.ts +297 -0
- package/src/core.ts +171 -0
- package/src/deslop/lex.ts +271 -0
- package/src/deslop/scan.ts +232 -0
- package/src/duration.ts +27 -0
- package/src/fanout-brief.ts +486 -0
- package/src/fanout-check.ts +455 -0
- package/src/fanout-landed.ts +370 -0
- package/src/fanout-stack.ts +108 -0
- package/src/fanout-status.ts +362 -0
- package/src/fanout-worktree.ts +893 -0
- package/src/fanout.ts +1473 -0
- package/src/forge/exec.ts +385 -0
- package/src/forge/github.ts +831 -0
- package/src/forge/gitlab.ts +906 -0
- package/src/forge/issue-forge.ts +209 -0
- package/src/forge/issue-link.ts +468 -0
- package/src/forge/issue-ops.ts +314 -0
- package/src/forge/merge-gate-base-red.ts +72 -0
- package/src/forge/pr-body.ts +100 -0
- package/src/forge/pr-edit.ts +292 -0
- package/src/forge/pr-ops.ts +1109 -0
- package/src/forge/pr-status.ts +10 -0
- package/src/forge/redact.ts +195 -0
- package/src/forge/report.ts +994 -0
- package/src/forge/reruns.ts +132 -0
- package/src/forge/resolve.ts +509 -0
- package/src/forge/types.ts +242 -0
- package/src/forge/verify.ts +398 -0
- package/src/git/default-base.ts +130 -0
- package/src/git/env-redirect.ts +28 -0
- package/src/git/facts.ts +122 -0
- package/src/git/ops.ts +1823 -0
- package/src/git/refs-read.ts +332 -0
- package/src/git/release.ts +1020 -0
- package/src/git/rev.ts +1175 -0
- package/src/git/session-hook.ts +597 -0
- package/src/git/stage.ts +63 -0
- package/src/hooks/agent-model.ts +149 -0
- package/src/hooks/budget-signal.ts +284 -0
- package/src/hooks/context.ts +461 -0
- package/src/hooks/cwd-normalize.ts +79 -0
- package/src/hooks/deadline.ts +228 -0
- package/src/hooks/delivery-verbs.ts +182 -0
- package/src/hooks/descriptor.ts +124 -0
- package/src/hooks/handle.ts +433 -0
- package/src/hooks/heavy-gate.ts +99 -0
- package/src/hooks/hook-runs.ts +130 -0
- package/src/hooks/hosts/claude-code.ts +380 -0
- package/src/hooks/hosts/codex.ts +372 -0
- package/src/hooks/hosts/cursor.ts +419 -0
- package/src/hooks/hosts/fields.ts +98 -0
- package/src/hooks/hosts/opencode.ts +79 -0
- package/src/hooks/hosts/pi.ts +78 -0
- package/src/hooks/index.ts +60 -0
- package/src/hooks/model-limit.ts +378 -0
- package/src/hooks/model-switch.ts +35 -0
- package/src/hooks/policy.ts +29 -0
- package/src/hooks/process.ts +48 -0
- package/src/hooks/protocol.ts +135 -0
- package/src/hooks/raw-git.ts +2289 -0
- package/src/hooks/repeat-failure.ts +115 -0
- package/src/hooks/run.ts +54 -0
- package/src/hooks/session-marker.ts +151 -0
- package/src/hooks/shell-words.ts +532 -0
- package/src/hooks/skill-nudge.ts +295 -0
- package/src/hooks/spawn-gate.ts +72 -0
- package/src/hooks/standing-signal.ts +99 -0
- package/src/hooks/standing.ts +225 -0
- package/src/hooks/steering.ts +74 -0
- package/src/hooks/stop.ts +408 -0
- package/src/hooks/worktree-confine.ts +415 -0
- package/src/hooks/write-gate.ts +1174 -0
- package/src/host-session.ts +459 -0
- package/src/knowledge.ts +368 -0
- package/src/ledger.ts +2913 -0
- package/src/stack.ts +2570 -0
- package/src/store/event-log.ts +217 -0
- package/src/store/notes.ts +29 -0
- package/src/store/patch.ts +135 -0
- package/src/store/paths.ts +368 -0
- package/src/store/reduce.ts +203 -0
- package/src/test-audit-mocks.ts +309 -0
- package/src/verdict-cover.ts +151 -0
- package/src/worktree-deps.ts +142 -0
- package/templates/headers.md +3 -0
- package/templates/hygiene/.editorconfig +8 -0
- package/templates/hygiene/.gitattributes +3 -0
- package/templates/hygiene/CHANGELOG.md +14 -0
- package/templates/hygiene/CONTRIBUTING.md +3 -0
- package/templates/hygiene/LICENSE +21 -0
- package/templates/hygiene/README.md +3 -0
- package/templates/issue-update.md +5 -0
- package/templates/plan-template.md +26 -0
- package/templates/spec-template.md +64 -0
- package/templates/workit-contract.md +13 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The check rollup a PR status may trust (#646 root): GitHub's
|
|
2
|
+
// `commits(last: 1)` can still describe an older commit for a moment after a
|
|
3
|
+
// push, so its rollup is that commit's result, not the head's. Read for the
|
|
4
|
+
// current head, a stale rollup would report the old run's success or pending.
|
|
5
|
+
|
|
6
|
+
/** The commit's rollup when that commit is the PR head, else null (no checks reported yet). */
|
|
7
|
+
export const rollupForHead = <R>(
|
|
8
|
+
commit: { oid: string; statusCheckRollup: R | null } | undefined,
|
|
9
|
+
headSha: string,
|
|
10
|
+
): R | null => (commit && commit.oid === headSha ? commit.statusCheckRollup : null);
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// Redaction and bounding for text that comes back from a forge: CI logs,
|
|
2
|
+
// review comments, CLI stderr. All of it is untrusted data that an agent will
|
|
3
|
+
// read, so secrets are masked and the size is capped before it leaves core.
|
|
4
|
+
|
|
5
|
+
const R = "[REDACTED]";
|
|
6
|
+
|
|
7
|
+
// Token formats that are secrets wherever they appear.
|
|
8
|
+
const TOKEN_PATTERNS: readonly RegExp[] = [
|
|
9
|
+
// GitHub tokens (classic, fine-grained, app, OAuth, refresh).
|
|
10
|
+
/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})/gu,
|
|
11
|
+
// GitLab tokens, by every prefix GitLab documents
|
|
12
|
+
// (https://docs.gitlab.com/security/tokens/#token-prefixes) and only those:
|
|
13
|
+
// a bare `gl…-` also matches words such as `global-setup-teardown-hook`.
|
|
14
|
+
/\b(?:glpat|gloas|gldt|glrtr|glrt|glcbt|glptt|glft|glimt|glagent|glwt|glsoat|glffct)-[A-Za-z0-9_-]{20,}/gu,
|
|
15
|
+
// GitLab's session cookie.
|
|
16
|
+
/\b_gitlab_session=[A-Za-z0-9%_-]{16,}/gu,
|
|
17
|
+
/\bnpm_[A-Za-z0-9]{30,}/gu,
|
|
18
|
+
/\bxox[abposr]-[A-Za-z0-9-]{10,}/gu,
|
|
19
|
+
/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu,
|
|
20
|
+
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{20,}/gu,
|
|
21
|
+
/\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/gu,
|
|
22
|
+
/\bAIza[0-9A-Za-z_-]{35}/gu,
|
|
23
|
+
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/gu,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// Slack/Discord webhooks: the path is the credential.
|
|
27
|
+
const WEBHOOK =
|
|
28
|
+
/\b(https?:\/\/(?:hooks\.slack\.com\/(?:services|workflows|triggers)|discord(?:app)?\.com\/api\/webhooks))\/[^\s"'<>]+/giu;
|
|
29
|
+
|
|
30
|
+
// Userinfo in any URL (`https://user:pw@`, `postgres://a:b:c@`, `https://tok@`).
|
|
31
|
+
const URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/giu;
|
|
32
|
+
|
|
33
|
+
// `curl -u user:pass`, `--user user:pass`.
|
|
34
|
+
const CURL_USER = /(\s(?:-u|--user)(?:\s+|=))(["']?)[^\s"':]+:[^\s"']+\2/gu;
|
|
35
|
+
|
|
36
|
+
// Secret-ish keys in env dumps, YAML, JSON, headers and connection strings:
|
|
37
|
+
// DB_PASSWORD=…, "access_token": "…", X-Api-Key: …, PRIVATE-TOKEN: …,
|
|
38
|
+
// AccountKey=…; an optional auth scheme (Basic/Bearer) is kept.
|
|
39
|
+
const KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
40
|
+
const ASSIGNMENT = new RegExp(
|
|
41
|
+
String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`,
|
|
42
|
+
"giu",
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const BEARER = /\b(bearer\s+)[A-Za-z0-9._~+/-]{12,}=*/giu;
|
|
46
|
+
|
|
47
|
+
// A quoted value may contain spaces: password: "correct horse battery".
|
|
48
|
+
const QUOTED_ASSIGNMENT = new RegExp(
|
|
49
|
+
String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`,
|
|
50
|
+
"giu",
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// ~/.netrc: `machine h login u password p` (also split across lines).
|
|
54
|
+
const NETRC = /\b((?:machine|login)\s+\S+\s+(?:login\s+\S+\s+)?password\s+)\S+/giu;
|
|
55
|
+
|
|
56
|
+
// Secrets passed as CLI flags: sshpass -p X, mysql -pX / -p X.
|
|
57
|
+
const FLAG_SECRETS: readonly RegExp[] = [
|
|
58
|
+
/\b(sshpass\s+-p\s*)\S+/gu,
|
|
59
|
+
/\b(mysql(?:dump|admin|import|show)?\b[^\n]*?\s-p)(?!\s|$)\S+/gu,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
// Signed-URL and query-string credentials (Azure SAS `sig=`, `token=`, …).
|
|
63
|
+
const QUERY_SECRET =
|
|
64
|
+
/([?&;](?:sig|signature|token|access_token|api_key|apikey|key|password|secret|client_secret|X-Amz-Signature|X-Goog-Signature)=)[^&\s#"']+/giu;
|
|
65
|
+
|
|
66
|
+
// A key block from BEGIN to END, or to the end of the text when truncated.
|
|
67
|
+
const PRIVATE_KEY =
|
|
68
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/gu;
|
|
69
|
+
|
|
70
|
+
// Long base64 runs: redacted when they decode to a credential, or sit on a
|
|
71
|
+
// line that decodes base64 (`echo … | base64 -d`).
|
|
72
|
+
const BASE64 = /[A-Za-z0-9+/]{32,}={0,2}/gu;
|
|
73
|
+
|
|
74
|
+
const ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
75
|
+
|
|
76
|
+
export const stripAnsi = (value: string): string => value.replace(ANSI, "");
|
|
77
|
+
|
|
78
|
+
const decodesToSecret = (blob: string): boolean => {
|
|
79
|
+
if (/^[0-9a-f]+$/iu.test(blob)) return false; // hex: commit/tree ids
|
|
80
|
+
let decoded: string;
|
|
81
|
+
try {
|
|
82
|
+
decoded = Buffer.from(blob, "base64").toString("utf8");
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
if (!/^[\x20-\x7e\s]+$/u.test(decoded)) return false;
|
|
87
|
+
return (
|
|
88
|
+
TOKEN_PATTERNS.some((pattern) => new RegExp(pattern.source, "u").test(decoded)) ||
|
|
89
|
+
/^[^\s:]{1,64}:[^\s]{6,}$/u.test(decoded.trim())
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/** Mask secrets and strip terminal escapes. Works on whole (multi-line) text. */
|
|
94
|
+
export function redactText(value: string): string {
|
|
95
|
+
let out = stripAnsi(value).replace(PRIVATE_KEY, "[REDACTED PRIVATE KEY]");
|
|
96
|
+
out = out.replace(WEBHOOK, `$1/${R}`);
|
|
97
|
+
out = out.replace(URL_USERINFO, `$1${R}@`);
|
|
98
|
+
for (const pattern of TOKEN_PATTERNS) out = out.replace(pattern, R);
|
|
99
|
+
out = out.replace(CURL_USER, `$1$2${R}$2`);
|
|
100
|
+
out = out.replace(BEARER, `$1${R}`);
|
|
101
|
+
out = out.replace(QUOTED_ASSIGNMENT, (match, lead: string, quote: string, secret: string) =>
|
|
102
|
+
secret.startsWith(R) ? match : `${lead}${quote}${R}${quote}`,
|
|
103
|
+
);
|
|
104
|
+
out = out.replace(NETRC, `$1${R}`);
|
|
105
|
+
for (const pattern of FLAG_SECRETS) out = out.replace(pattern, `$1${R}`);
|
|
106
|
+
out = out.replace(QUERY_SECRET, `$1${R}`);
|
|
107
|
+
out = out.replace(
|
|
108
|
+
ASSIGNMENT,
|
|
109
|
+
(match, lead: string, scheme: string | undefined, secret: string) =>
|
|
110
|
+
secret.startsWith(R) ? match : `${lead}${scheme ?? ""}${R}`,
|
|
111
|
+
);
|
|
112
|
+
return out
|
|
113
|
+
.split("\n")
|
|
114
|
+
.map((line) =>
|
|
115
|
+
line.replace(BASE64, (blob) =>
|
|
116
|
+
/\bbase64\b/iu.test(line) || decodesToSecret(blob) ? R : blob,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
.join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// GitHub Actions prefixes every log line with an ISO timestamp; GitLab traces
|
|
123
|
+
// carry section markers and a leading timestamp/stream tag in newer runners.
|
|
124
|
+
const LINE_NOISE = /^(?:\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z\s?)?(?:\d{2}[A-Z]\s)?/u;
|
|
125
|
+
const GITLAB_SECTION = /section_(?:start|end):\d+:[A-Za-z0-9_.-]+(?:\[[^\]]*\])?\r?/gu;
|
|
126
|
+
|
|
127
|
+
// Where the failure ends: everything after is runner cleanup.
|
|
128
|
+
const FAILURE_END = [/##\[error\]Process completed with exit code/u, /^ERROR: Job failed\b/u];
|
|
129
|
+
|
|
130
|
+
const MAX_LINE = 400;
|
|
131
|
+
|
|
132
|
+
// The first line that looks like the actual failure: a strong marker (a
|
|
133
|
+
// test-runner failure, an assertion, a thrown error) wins over a weak one
|
|
134
|
+
// (any "failed"/"ERROR" mention, which passing tests also print).
|
|
135
|
+
const STRONG_ERROR =
|
|
136
|
+
/^\s*(?:\(fail\)|FAIL\b|✗|✕|×|error(?:\[[A-Z0-9]+\])?:|[A-Za-z]*Error\b:|AssertionError|Traceback|panic:|Exception\b)/u;
|
|
137
|
+
const WEAK_ERROR = /\berror\b[:[]|\bERROR\b|\bFAIL(?:ED)?\b|\bfailed\b|\bassert/u;
|
|
138
|
+
// Passing test lines and summaries mention failure words without failing.
|
|
139
|
+
const NOT_FIRST_ERROR =
|
|
140
|
+
/^\s*(?:\(pass\)|\(skip\)|\(todo\)|✓|✔|ok\b|PASS\b)|^\s*\d+ fail\b|##\[error\]Process completed|^\s*0 (?:errors?|fail)/u;
|
|
141
|
+
|
|
142
|
+
/** Lines of context kept before the first error. */
|
|
143
|
+
const LEAD = 3;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A bounded, redacted view of a CI log: at most `lines` lines. Runner
|
|
147
|
+
* cleanup after the failure marker is dropped. When the first error sits
|
|
148
|
+
* before the final window, the view is the region around that first error,
|
|
149
|
+
* a `…` gap, then the end of the log (where the summary and exit code are).
|
|
150
|
+
*/
|
|
151
|
+
export function logTail(text: string, lines: number): string[] {
|
|
152
|
+
if (lines <= 0) return [];
|
|
153
|
+
const cleaned = text
|
|
154
|
+
.replace(GITLAB_SECTION, "")
|
|
155
|
+
.split(/\r?\n/u)
|
|
156
|
+
// A bare \r rewrites the line in a terminal (progress bars): keep what
|
|
157
|
+
// was finally shown.
|
|
158
|
+
.map(
|
|
159
|
+
(raw) =>
|
|
160
|
+
stripAnsi(raw)
|
|
161
|
+
.split("\r")
|
|
162
|
+
.findLast((part) => part.trim()) ?? "",
|
|
163
|
+
)
|
|
164
|
+
.map((raw) => raw.replace(LINE_NOISE, ""));
|
|
165
|
+
// Redact the whole text so a secret spanning lines (a private key) goes.
|
|
166
|
+
const all = redactText(cleaned.join("\n")).split("\n");
|
|
167
|
+
let end = all.length;
|
|
168
|
+
for (let index = all.length - 1; index >= 0; index -= 1)
|
|
169
|
+
if (FAILURE_END.some((pattern) => pattern.test(all[index]))) {
|
|
170
|
+
end = index + 1;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const kept = all.slice(0, end);
|
|
174
|
+
while (kept.length > 0 && !kept[kept.length - 1].trim()) kept.pop();
|
|
175
|
+
const cap = (value: string) => (value.length > MAX_LINE ? `${value.slice(0, MAX_LINE)}…` : value);
|
|
176
|
+
const tailStart = Math.max(0, kept.length - lines);
|
|
177
|
+
const find = (pattern: RegExp) =>
|
|
178
|
+
kept.findIndex((value) => pattern.test(value) && !NOT_FIRST_ERROR.test(value));
|
|
179
|
+
const strong = find(STRONG_ERROR);
|
|
180
|
+
const first = strong >= 0 ? strong : find(WEAK_ERROR);
|
|
181
|
+
if (first < 0 || first >= tailStart || lines < 8) return kept.slice(tailStart).map(cap);
|
|
182
|
+
const headCount = Math.ceil((lines - 1) * (2 / 3));
|
|
183
|
+
const from = Math.max(0, first - LEAD);
|
|
184
|
+
const head = kept.slice(from, from + headCount);
|
|
185
|
+
const tailCount = lines - 1 - head.length;
|
|
186
|
+
const tailFrom = Math.max(from + head.length, kept.length - tailCount);
|
|
187
|
+
const tail = kept.slice(tailFrom);
|
|
188
|
+
return [...head, ...(tailFrom > from + head.length ? ["…"] : []), ...tail].map(cap);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** One-line, redacted, length-capped text (review comment bodies). */
|
|
192
|
+
export function shortBody(value: string, max = 300): string {
|
|
193
|
+
const flat = redactText(value).replace(/\s+/gu, " ").trim();
|
|
194
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
195
|
+
}
|