portable-agent-layer 0.54.3 → 0.55.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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: analyze-pdf
3
- description: Download and analyze PDF files from URLs or local paths — extract text, answer questions, summarize content. Use when analyzing, reading, or extracting information from a PDF.
3
+ description: "Download and analyze PDF files from URLs or local paths — extract text, answer questions, summarize content. Use when analyzing, reading, or extracting information from a PDF."
4
4
  argument-hint: <URL or file path>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: analyze-youtube
3
- description: Analyze YouTube videos using Gemini's native video understanding — summarize, extract insights, answer questions. Use when analyzing, summarizing, or extracting information from a YouTube video.
3
+ description: "Analyze YouTube videos using Gemini's native video understanding — summarize, extract insights, answer questions. Use when analyzing, summarizing, or extracting information from a YouTube video."
4
4
  argument-hint: <YouTube URL>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: consulting-report
3
- description: Build a beautifully-typeset consulting-report PDF from a typed data file and a React layout. Use when generating an assessment, strategic review, operational readiness check, or any McKinsey-style consulting deliverable as a PDF.
3
+ description: "Build a beautifully-typeset consulting-report PDF from a typed data file and a React layout. Use when generating an assessment, strategic review, operational readiness check, or any McKinsey-style consulting deliverable as a PDF."
4
4
  argument-hint: scaffold <target-dir> | dev <report-dir> | <report-dir> (render PDF)
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: council
3
- description: Multi-perspective parallel debate on a decision — 3-5 independent perspectives argue in parallel, then synthesize into a verdict. Use when debating, weighing options, or needing multiple viewpoints on a question.
3
+ description: "Multi-perspective parallel debate on a decision — 3-5 independent perspectives argue in parallel, then synthesize into a verdict. Use when debating, weighing options, or needing multiple viewpoints on a question."
4
4
  argument-hint: <question or decision>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: create-pdf
3
- description: Convert markdown files into a styled PDF. Use when creating a PDF from existing markdown files, combining markdown into a report, or converting .md to .pdf.
3
+ description: "Convert markdown files into a styled PDF. Use when creating a PDF from existing markdown files, combining markdown into a report, or converting .md to .pdf."
4
4
  argument-hint: <file paths, glob pattern, or directory containing .md files>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: create-skill
3
- description: Create a new personal skill for this user, scaffolded into their own ~/.pal/skills/ and linked into every installed agent. Use when the user asks to create a skill, add a capability, build a custom command, or "make a skill that".
3
+ description: "Create a new personal skill for this user, scaffolded into their own ~/.pal/skills/ and linked into every installed agent. Use when the user asks to create a skill, add a capability, build a custom command, or \"make a skill that…\"."
4
4
  argument-hint: <skill name> <skill description>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: entities
3
- description: Maintain the personal knowledge graph of people and companies. Detect named entities in any content (article, video, paste, conversation), upsert them to ~/.pal/memory/knowledge/, and surface what's already known. Use proactively whenever named entities appear — don't wait to be asked.
3
+ description: "Maintain the personal knowledge graph of people and companies. Detect named entities in any content (article, video, paste, conversation), upsert them to ~/.pal/memory/knowledge/, and surface what's already known. Use proactively whenever named entities appear — don't wait to be asked."
4
4
  argument-hint: <content, URL, or pasted text>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: extract-wisdom
3
- description: Extract structured insights from content (articles, videos, podcasts). Use when extracting wisdom, key takeaways, or structured insights from any content.
3
+ description: "Extract structured insights from content (articles, videos, podcasts). Use when extracting wisdom, key takeaways, or structured insights from any content."
4
4
  argument-hint: <content or URL>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: first-principles
3
- description: Break down a problem to its fundamental constraints and build up a solution. Use when decomposing complexity, challenging assumptions, or finding root causes.
3
+ description: "Break down a problem to its fundamental constraints and build up a solution. Use when decomposing complexity, challenging assumptions, or finding root causes."
4
4
  argument-hint: <problem>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: frontend-design
3
- description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
3
+ description: "Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics."
4
4
  license: Complete terms in LICENSE.txt
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: fyzz-chat-api
3
- description: Query Fyzz Chat conversations and projects via the REST API. Use when looking up conversations, searching chat history, or listing projects in Fyzz Chat.
3
+ description: "Query Fyzz Chat conversations and projects via the REST API. Use when looking up conversations, searching chat history, or listing projects in Fyzz Chat."
4
4
  argument-hint: <conversations|projects> [options]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pal-analyze
3
- description: Run learning analysis — surface rating trends, recurring failure patterns, and graduation candidates. Use when learning analysis is due, or when the user asks about performance patterns, low ratings, or what to improve.
3
+ description: "Run learning analysis — surface rating trends, recurring failure patterns, and graduation candidates. Use when learning analysis is due, or when the user asks about performance patterns, low ratings, or what to improve."
4
4
  argument-hint: [optional: --actionable for AI-generated recommendations]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pal-reflect
3
- description: Run relationship reflect — promote recurring behavioral observations into tracked opinions. Use when relationship reflect is due, or when the user asks to review what patterns have been observed.
3
+ description: "Run relationship reflect — promote recurring behavioral observations into tracked opinions. Use when relationship reflect is due, or when the user asks to review what patterns have been observed."
4
4
  argument-hint: [optional: --dry-run to preview without writing]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playwright
3
- description: Capture a screenshot of a URL or local page and load it into context for a visual check. Use when asked to check visually, use playwright, screenshot a page, see or look at the design yourself, or verify a layout on desktop and mobile widths.
3
+ description: "Capture a screenshot of a URL or local page and load it into context for a visual check. Use when asked to check visually, use playwright, screenshot a page, see or look at the design yourself, or verify a layout on desktop and mobile widths."
4
4
  argument-hint: <url> [--viewport WxH] [--full-page] [--selector <css>]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: presentation
3
- description: "Build branded HTML presentations from markdown using Reveal.js. Multi-template registry per user (each template = brand color, logo, fonts, footer, aspect). Per-deck workflow: scaffold → edit one markdown file per slide in slides/ → build → present. Output: a `<deck-name>/` subdir with a self-contained HTML and a concatenated markdown sibling. 14 layouts including data-display patterns (big-stat, metric-grid). Use when creating slide decks, talks, workshop slides, lectures, or pitch decks."
3
+ description: "Build branded HTML presentations from markdown using Reveal.js. Multi-template registry per user (each template = brand color, logo, fonts, footer, aspect). Per-deck workflow: scaffold → edit one markdown file per slide in slides/ → build → present. Output: a per-deck subdir with a self-contained HTML and a concatenated markdown sibling. 14 layouts including data-display patterns (big-stat, metric-grid). Use when creating slide decks, talks, workshop slides, lectures, or pitch decks."
4
4
  argument-hint: <deck-dir> to build, OR `setup-template` to add a brand template, OR `new <deck-dir> --template <name>` to scaffold a deck, OR `list-templates`
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: projects
3
- description: Project context management. PROACTIVE — use when the user references a project (by name or as "this repo", "current work"), asks to add/update/complete a project, says "store under <project>", "track this", "what am I working on", "my projects", "my priorities".
3
+ description: "Project context management. PROACTIVE — use when the user references a project (by name or as \"this repo\", \"current work\"), asks to add/update/complete a project, says \"store under a named project\", \"track this\", \"what am I working on\", \"my projects\", \"my priorities\". Also triggered by ISC/ISA terminology — ISC stands for Ideal State Criteria, a project's verifiable done-conditions stored as `ISC-N:` lines in the Criteria section of its ISA.md spec. Phrases like \"ISC\", \"open/opening the ISC\", \"open ISCs\" (unfinished criteria), \"create/creating a ticket for a project\", \"new ticket\", or \"project ticket\" all map here: use `list-isc` to read them, `add-isc` to create one, `complete-isc`/`reopen-isc` to change status."
4
4
  argument-hint: [list | create | resume | add-next | add-blocker | add-decision | add-handoff | update-section | criteria | isa-init | complete | archive | pause]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: reflect
3
- description: Diagnose why a PAL behavior did not trigger as expected — trace hooks, instructions, and logic to find the gap. Use when a hook, skill, or automation didn't fire or behaved unexpectedly.
3
+ description: "Diagnose why a PAL behavior did not trigger as expected — trace hooks, instructions, and logic to find the gap. Use when a hook, skill, or automation didn't fire or behaved unexpectedly."
4
4
  argument-hint: <what went wrong>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: research
3
- description: Multi-agent parallel research — quick/standard/extensive modes with specialized researcher agents for depth, breadth, and verification. Use when researching a topic, finding information, or investigating something thoroughly.
3
+ description: "Multi-agent parallel research — quick/standard/extensive modes with specialized researcher agents for depth, breadth, and verification. Use when researching a topic, finding information, or investigating something thoroughly."
4
4
  argument-hint: <topic or question>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: review
3
- description: Security-focused code review with severity ratings. Use when reviewing code for security issues, vulnerabilities, or OWASP concerns.
3
+ description: "Security-focused code review with severity ratings. Use when reviewing code for security issues, vulnerabilities, or OWASP concerns."
4
4
  argument-hint: [file or directory]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: summarize
3
- description: Structured summarization of documents, URLs, or conversations. Use when summarizing content, creating overviews, or distilling key points.
3
+ description: "Structured summarization of documents, URLs, or conversations. Use when summarizing content, creating overviews, or distilling key points."
4
4
  argument-hint: <document, URL, or topic>
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: telos
3
- description: Personal context management. Use when discussing goals, beliefs, challenges, identity, updating telos, life context, changing a goal, what do I believe, current obstacles, mission, or strategies.
3
+ description: "Personal context management. Use when discussing goals, beliefs, challenges, identity, updating telos, life context, changing a goal, what do I believe, current obstacles, mission, or strategies."
4
4
  argument-hint: [area to view or update]
5
5
  ---
6
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: think
3
- description: Thinking mode router — selects the right analytical approach for a question. Use when thinking through a problem, analyzing deeply, brainstorming ideas, debating options, decomposing to root cause, challenging assumptions, or exploring from multiple perspectives.
3
+ description: "Thinking mode router — selects the right analytical approach for a question. Use when thinking through a problem, analyzing deeply, brainstorming ideas, debating options, decomposing to root cause, challenging assumptions, or exploring from multiple perspectives."
4
4
  argument-hint: <question or topic>
5
5
  ---
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.54.3",
3
+ "version": "0.55.0",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -1087,10 +1087,12 @@ async function install(targets: Targets) {
1087
1087
  const { scaffoldTelos, scaffoldPalSettings } = await import("../targets/lib");
1088
1088
  const { promptIdentity } = await import("./setup-identity");
1089
1089
  const { promptTelos } = await import("./setup-telos");
1090
+ const { promptAttribution } = await import("./setup-attribution");
1090
1091
  scaffoldTelos();
1091
1092
  scaffoldPalSettings();
1092
1093
  await promptIdentity();
1093
1094
  await promptTelos();
1095
+ await promptAttribution();
1094
1096
 
1095
1097
  if (targets.claude) {
1096
1098
  console.log("━━━ Claude Code ━━━");
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Optional git co-author attribution — one-time opt-in prompt.
3
+ *
4
+ * Asked once during `pal install` (covers fresh init and the reinstall that
5
+ * `pal cli update` runs). The `decided` flag makes it ask exactly once: new
6
+ * users see it at init, existing users see it on their next update, nobody is
7
+ * nagged again. The choice is applied to ~/.claude/settings.json at install time.
8
+ */
9
+
10
+ import * as clack from "@clack/prompts";
11
+ import {
12
+ identity,
13
+ raw as readSettings,
14
+ write as writeSettings,
15
+ } from "../hooks/lib/settings";
16
+ import { buildAttributionText } from "../targets/lib";
17
+
18
+ /** Prompt once for git attribution opt-in. No-op if already decided or non-TTY. */
19
+ export async function promptAttribution(): Promise<void> {
20
+ if (!process.stdin.isTTY) return;
21
+
22
+ const settings = { ...readSettings() };
23
+ if (settings.attribution?.decided) return;
24
+
25
+ const name = identity().ai.name;
26
+ const { commit, pr } = buildAttributionText(name);
27
+
28
+ clack.intro("Git attribution");
29
+ clack.note(
30
+ `commit: ${commit}\npr: ${pr}`,
31
+ `Credit ${name} on the commits & PRs it makes?`
32
+ );
33
+
34
+ const enabled = await clack.confirm({
35
+ message: `Add this ${name} co-author credit?`,
36
+ initialValue: true,
37
+ });
38
+ if (clack.isCancel(enabled)) {
39
+ clack.cancel("Skipped — will ask again next time");
40
+ return;
41
+ }
42
+
43
+ settings.attribution = { enabled: enabled === true, decided: true };
44
+ writeSettings(settings);
45
+ const state = enabled ? `${name} attribution on` : "Attribution off";
46
+ clack.outro(
47
+ `${state} ✓ · change later: edit pal-settings.json, then run 'pal install'`
48
+ );
49
+ }
@@ -23,6 +23,8 @@ export interface PalSettingsData {
23
23
  };
24
24
  loadAtStartup?: { files?: string[] };
25
25
  dynamicContext?: Record<string, boolean>;
26
+ /** Git co-author attribution opt-in. `decided` gates the one-time prompt. */
27
+ attribution?: { enabled?: boolean; decided?: boolean };
26
28
  [key: string]: unknown;
27
29
  }
28
30
 
@@ -8,8 +8,10 @@ import { copyFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
8
8
  import { resolve } from "node:path";
9
9
  import { regenerateIfNeeded } from "../../hooks/lib/claude-md";
10
10
  import { assets, palHome, palPkg, platform } from "../../hooks/lib/paths";
11
+ import { identity, raw as readPalSettings } from "../../hooks/lib/settings";
11
12
  import {
12
13
  addStatuslineConfig,
14
+ applyAttribution,
13
15
  copyAgents,
14
16
  copyPalDocs,
15
17
  copySkills,
@@ -50,6 +52,16 @@ let merged = mergeSettings(existing, template);
50
52
  // Add platform-specific statusLine config
51
53
  merged = addStatuslineConfig(merged);
52
54
 
55
+ // Apply the user's git-attribution choice (composed from identity name at runtime —
56
+ // never committed to assets). Only acts once the opt-in prompt has been answered.
57
+ const attribution = readPalSettings().attribution;
58
+ if (attribution?.decided) {
59
+ merged = applyAttribution(merged, {
60
+ enabled: attribution.enabled === true,
61
+ name: identity().ai.name,
62
+ });
63
+ }
64
+
53
65
  writeJson(SETTINGS, merged);
54
66
  log.success("Merged PAL settings into settings.json");
55
67
 
@@ -43,6 +43,42 @@ export function writeJson(path: string, data: unknown): void {
43
43
  writeFileSync(path, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
44
44
  }
45
45
 
46
+ // --- Git attribution ---
47
+
48
+ /** Public PAL repository — the link surfaced in commit/PR co-author credits. */
49
+ export const PAL_REPO_URL = "https://github.com/kovrichard/portable-agent-layer";
50
+
51
+ /** Build the commit footer (bare URL, autolinks on GitHub) and PR body line (markdown link). */
52
+ export function buildAttributionText(
53
+ name: string,
54
+ repoUrl: string = PAL_REPO_URL
55
+ ): { commit: string; pr: string } {
56
+ return {
57
+ commit: `Co-authored by ${name} · ${repoUrl}`,
58
+ pr: `Co-authored by [${name}](${repoUrl})`,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Apply the user's git-attribution choice to a Claude settings object.
64
+ * Enabled → fill attribution.commit/pr and drop Claude's own byline (includeCoAuthoredBy:false).
65
+ * Disabled → clear PAL attribution and restore Claude's default byline.
66
+ */
67
+ export function applyAttribution(
68
+ settings: Settings,
69
+ opts: { enabled: boolean; name: string; repoUrl?: string }
70
+ ): Settings {
71
+ const result = { ...settings };
72
+ if (opts.enabled) {
73
+ result.attribution = buildAttributionText(opts.name, opts.repoUrl);
74
+ result.includeCoAuthoredBy = false;
75
+ } else {
76
+ result.attribution = { commit: "", pr: "" };
77
+ if (result.includeCoAuthoredBy === false) delete result.includeCoAuthoredBy;
78
+ }
79
+ return result;
80
+ }
81
+
46
82
  /** Resolve the VS Code user settings.json path cross-platform. Returns null on unknown platforms. */
47
83
  export function vscodeSettingsFile(): string | null {
48
84
  const h = homedir();
@@ -32,6 +32,7 @@ export interface DoctorReport {
32
32
  interface ParsedSkill {
33
33
  name: string | null;
34
34
  description: string | null;
35
+ descriptionQuoted: boolean;
35
36
  body: string;
36
37
  }
37
38
 
@@ -44,13 +45,19 @@ const MAX_BODY_LINES = 500;
44
45
  function parseSkill(content: string): ParsedSkill {
45
46
  const parts = content.split(/^---\s*$/m);
46
47
  if (parts.length < 3) {
47
- return { name: null, description: null, body: content };
48
+ return { name: null, description: null, descriptionQuoted: false, body: content };
48
49
  }
49
50
  const frontmatter = parts[1];
50
51
  const body = parts.slice(2).join("---");
51
52
  const name = /^name:\s*"?(.+?)"?\s*$/m.exec(frontmatter)?.[1] ?? null;
52
- const description = /^description:\s*"?(.+?)"?\s*$/m.exec(frontmatter)?.[1] ?? null;
53
- return { name, description, body };
53
+ const rawDescription = /^description:[ \t]*(.*?)\s*$/m.exec(frontmatter)?.[1] ?? null;
54
+ const descriptionQuoted =
55
+ rawDescription !== null &&
56
+ rawDescription.length >= 2 &&
57
+ rawDescription.startsWith('"') &&
58
+ rawDescription.endsWith('"');
59
+ const description = descriptionQuoted ? rawDescription.slice(1, -1) : rawDescription;
60
+ return { name, description, descriptionQuoted, body };
54
61
  }
55
62
 
56
63
  /** Remove fenced and inline code so prose checks don't trip on examples. */
@@ -89,7 +96,7 @@ export function lintSkill(skillDir: string): DoctorReport {
89
96
  `skill file is "${skillFile}" — must be exactly "SKILL.md" or the skill is silently ignored`
90
97
  );
91
98
 
92
- const { name, description, body } = parseSkill(
99
+ const { name, description, descriptionQuoted, body } = parseSkill(
93
100
  readFileSync(resolve(skillDir, skillFile), "utf-8")
94
101
  );
95
102
 
@@ -141,6 +148,13 @@ export function lintSkill(skillDir: string): DoctorReport {
141
148
  "description.length",
142
149
  `${description.length} chars exceeds ${MAX_DESCRIPTION}`
143
150
  );
151
+ descriptionQuoted
152
+ ? add("pass", "description.quoted", "value is wrapped in double quotes")
153
+ : add(
154
+ "warn",
155
+ "description.quoted",
156
+ 'value is not wrapped in double quotes — unquoted YAML mis-parses on colons, commas, and quotes; wrap it in "..." (escaping any inner " as \\")'
157
+ );
144
158
  /<[^>]+>/.test(description)
145
159
  ? add(
146
160
  "warn",
@@ -189,15 +203,21 @@ export function lintSkill(skillDir: string): DoctorReport {
189
203
 
190
204
  // ── reference depth (one level deep) ──
191
205
  const linkRe = /\[[^\]]+\]\(([^)]+\.md)\)/g;
192
- const hasMdLink = /\[[^\]]+\]\([^)]+\.md\)/;
206
+ const skillFilePath = resolve(skillDir, skillFile);
207
+ // A reference back to the entry SKILL.md is a return-link, not a deeper chain.
208
+ const isDeeperRef = (target: string) =>
209
+ !/^https?:/.test(target) && resolve(skillDir, target) !== skillFilePath;
193
210
  let nested = false;
194
211
  for (const m of body.matchAll(linkRe)) {
195
212
  const target = m[1];
196
- if (/^https?:/.test(target)) continue;
213
+ if (!isDeeperRef(target)) continue;
197
214
  const targetPath = resolve(skillDir, target);
198
215
  if (!existsSync(targetPath)) continue;
199
216
  const refContent = readFileSync(targetPath, "utf-8");
200
- if (hasMdLink.test(refContent)) {
217
+ const onward = [...refContent.matchAll(linkRe)]
218
+ .map((mm) => mm[1])
219
+ .filter(isDeeperRef);
220
+ if (onward.length > 0) {
201
221
  nested = true;
202
222
  add(
203
223
  "warn",
@@ -209,7 +229,12 @@ export function lintSkill(skillDir: string): DoctorReport {
209
229
  if (!nested) add("pass", "references.depth", "references are one level deep");
210
230
 
211
231
  // ── windows-style paths ──
212
- /\b[\w.-]+\\[\w.-]+\.(py|ts|js|sh|md|json)\b/.test(body)
232
+ // Skip lines that are deliberate Windows examples (cmd.exe / %ENV% paths)
233
+ // a cross-platform skill may legitimately document the Windows invocation.
234
+ const winPathRe = /\b[\w.-]+\\[\w.-]+\.(py|ts|js|sh|md|json)\b/;
235
+ const isIntentionalWindows = (line: string) =>
236
+ /%[A-Z_]+%/.test(line) || /cmd\.exe/i.test(line);
237
+ body.split("\n").some((l) => winPathRe.test(l) && !isIntentionalWindows(l))
213
238
  ? add("warn", "paths", "Windows-style backslash path found — use forward slashes")
214
239
  : add("pass", "paths", "forward-slash paths");
215
240