dsh-pstack 0.0.0-stage → 0.2.0-beta.1
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 +22 -0
- package/README.md +133 -2
- package/assets/agents/comment-sicko.md +32 -0
- package/assets/agents/poteto-agent.md +8 -0
- package/assets/docs/guide/01-setup.md +64 -0
- package/assets/docs/guide/02-poteto-mode.md +139 -0
- package/assets/docs/guide/03-understand.md +79 -0
- package/assets/docs/guide/04-design.md +133 -0
- package/assets/docs/guide/05-build-and-clean.md +83 -0
- package/assets/docs/guide/06-verify-and-ship.md +152 -0
- package/assets/docs/guide/07-overnight.md +120 -0
- package/assets/docs/guide/08-principles.md +72 -0
- package/assets/docs/guide/09-make-it-yours.md +96 -0
- package/assets/docs/guide/10-recipes-and-pitfalls.md +180 -0
- package/assets/docs/guide/README.md +38 -0
- package/assets/docs/guide/images/design.jpg +0 -0
- package/assets/docs/guide/images/overnight.jpg +0 -0
- package/assets/docs/guide/images/recipes.jpg +0 -0
- package/assets/docs/guide/images/router.jpg +0 -0
- package/assets/docs/guide/images/understanding.jpg +0 -0
- package/assets/docs/guide/images/verification.jpg +0 -0
- package/assets/docs/harness/dsh.md +60 -0
- package/assets/docs/pstack-models.example.md +54 -0
- package/assets/skills/architect/SKILL.md +81 -0
- package/assets/skills/architect/references/design-red-flags.md +57 -0
- package/assets/skills/architect/references/rationale-template.md +35 -0
- package/assets/skills/architect/references/runner-prompt.md +20 -0
- package/assets/skills/arena/SKILL.md +50 -0
- package/assets/skills/automate-me/SKILL.md +106 -0
- package/assets/skills/benchmark-checklist/SKILL.md +39 -0
- package/assets/skills/blast-radius/SKILL.md +52 -0
- package/assets/skills/bro/SKILL.md +7 -0
- package/assets/skills/correct/SKILL.md +32 -0
- package/assets/skills/create-formal-verification-skill/SKILL.md +43 -0
- package/assets/skills/create-verification-skill/SKILL.md +46 -0
- package/assets/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/assets/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/assets/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/assets/skills/figure-it-out/SKILL.md +54 -0
- package/assets/skills/how/SKILL.md +38 -0
- package/assets/skills/how/references/explainer-prompt.md +55 -0
- package/assets/skills/how/references/explorer-prompt.md +35 -0
- package/assets/skills/in-your-own-words/SKILL.md +7 -0
- package/assets/skills/interrogate/SKILL.md +71 -0
- package/assets/skills/interrogate/references/code-quality-review.md +47 -0
- package/assets/skills/interrogate/references/lead-judgment.md +39 -0
- package/assets/skills/interrogate/references/reviewer-prompt.md +62 -0
- package/assets/skills/interrogate/references/rubric.md +77 -0
- package/assets/skills/ketchup/SKILL.md +45 -0
- package/assets/skills/maintain-formal-verification-skill/SKILL.md +60 -0
- package/assets/skills/maintain-verification-skill/SKILL.md +41 -0
- package/assets/skills/no-comments/SKILL.md +26 -0
- package/assets/skills/poteto-help/SKILL.md +162 -0
- package/assets/skills/poteto-help/references/prompting.md +51 -0
- package/assets/skills/poteto-help/references/recipes.md +51 -0
- package/assets/skills/poteto-mode/SKILL.md +151 -0
- package/assets/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/assets/skills/poteto-mode/playbooks/autonomous-run.md +15 -0
- package/assets/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/assets/skills/poteto-mode/playbooks/autopilot-stack.md +12 -0
- package/assets/skills/poteto-mode/playbooks/babysit.md +23 -0
- package/assets/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/assets/skills/poteto-mode/playbooks/eval.md +25 -0
- package/assets/skills/poteto-mode/playbooks/feature.md +21 -0
- package/assets/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/assets/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/assets/skills/poteto-mode/playbooks/multi-phase-plan.md +155 -0
- package/assets/skills/poteto-mode/playbooks/opening-a-pr.md +34 -0
- package/assets/skills/poteto-mode/playbooks/orchestrate.md +112 -0
- package/assets/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/assets/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/assets/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/assets/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/assets/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/assets/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/assets/skills/poteto-mode/playbooks/shipping.md +12 -0
- package/assets/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/assets/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/assets/skills/poteto-mode/playbooks/worktree-cleanup.md +20 -0
- package/assets/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/assets/skills/poteto-mode/scripts/check-plan.mjs +220 -0
- package/assets/skills/poteto-mode/scripts/check-plan.test.mjs +343 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.mjs +4282 -0
- package/assets/skills/poteto-mode/scripts/orch/orch.test.mjs +2042 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/cli.test.mjs +5101 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/github.test.mjs +1177 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/policy.test.mjs +1441 -0
- package/assets/skills/poteto-mode/scripts/watch-pr/watch-pr.mjs +4601 -0
- package/assets/skills/poteto-mode/scripts/worktree-audit.mjs +675 -0
- package/assets/skills/principle-attack-the-premise/SKILL.md +21 -0
- package/assets/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/assets/skills/principle-build-the-lever/SKILL.md +23 -0
- package/assets/skills/principle-encode-lessons-in-structure/SKILL.md +11 -0
- package/assets/skills/principle-exhaust-the-design-space/SKILL.md +9 -0
- package/assets/skills/principle-experience-first/SKILL.md +19 -0
- package/assets/skills/principle-explain-the-number/SKILL.md +17 -0
- package/assets/skills/principle-fix-root-causes/SKILL.md +21 -0
- package/assets/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/assets/skills/principle-guard-the-context-window/SKILL.md +12 -0
- package/assets/skills/principle-laziness-protocol/SKILL.md +10 -0
- package/assets/skills/principle-make-operations-idempotent/SKILL.md +22 -0
- package/assets/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +14 -0
- package/assets/skills/principle-minimize-reader-load/SKILL.md +18 -0
- package/assets/skills/principle-model-the-domain/SKILL.md +24 -0
- package/assets/skills/principle-never-block-on-the-human/SKILL.md +15 -0
- package/assets/skills/principle-outcome-oriented-execution/SKILL.md +21 -0
- package/assets/skills/principle-prove-it-works/SKILL.md +24 -0
- package/assets/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/assets/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/assets/skills/principle-sequence-verifiable-units/SKILL.md +17 -0
- package/assets/skills/principle-subtract-before-you-add/SKILL.md +18 -0
- package/assets/skills/principle-test-behavior-not-implementation/SKILL.md +20 -0
- package/assets/skills/principle-type-system-discipline/SKILL.md +13 -0
- package/assets/skills/recall/SKILL.md +37 -0
- package/assets/skills/reflect/SKILL.md +69 -0
- package/assets/skills/reflect/references/divergent-reviewer.md +38 -0
- package/assets/skills/reflect/references/judgment-reviewer.md +37 -0
- package/assets/skills/reflect/references/synthesizer.md +56 -0
- package/assets/skills/reflect/references/tooling-reviewer.md +48 -0
- package/assets/skills/setup-pstack/SKILL.md +67 -0
- package/assets/skills/sharpen/SKILL.md +16 -0
- package/assets/skills/show-me-your-work/SKILL.md +84 -0
- package/assets/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/assets/skills/show-me-your-work/scripts/log.mjs +108 -0
- package/assets/skills/swarm/SKILL.md +43 -0
- package/assets/skills/tdd/SKILL.md +42 -0
- package/assets/skills/teach/SKILL.md +23 -0
- package/assets/skills/technical-writing/SKILL.md +108 -0
- package/assets/skills/typescript-best-practices/SKILL.md +34 -0
- package/assets/skills/typescript-best-practices/references/patterns.md +328 -0
- package/assets/skills/unslop/SKILL.md +62 -0
- package/assets/skills/why/SKILL.md +100 -0
- package/assets/skills/why/references/epistemics.md +71 -0
- package/assets/skills/why/references/investigator-prompt.md +84 -0
- package/assets/skills/why/references/source-playbook.md +17 -0
- package/assets/skills/why/references/sources/code-archaeology.md +51 -0
- package/assets/skills/why/references/sources/databricks.md +70 -0
- package/assets/skills/why/references/sources/datadog.md +97 -0
- package/assets/skills/why/references/sources/incident-postmortem.md +5 -0
- package/assets/skills/why/references/sources/linear.md +48 -0
- package/assets/skills/why/references/sources/notion.md +55 -0
- package/assets/skills/why/references/sources/sentry.md +66 -0
- package/assets/skills/why/references/sources/slack.md +17 -0
- package/assets/skills/why/references/synthesizer-prompt.md +129 -0
- package/cordis.patch.yml +39 -0
- package/dist/frontmatter-BQX3ekwP.mjs +229 -0
- package/dist/frontmatter-BQX3ekwP.mjs.map +1 -0
- package/dist/history.d.mts +429 -0
- package/dist/history.mjs +1129 -0
- package/dist/history.mjs.map +1 -0
- package/dist/host-contract-o47JH_r8.d.mts +527 -0
- package/dist/index.d.mts +278 -0
- package/dist/index.mjs +272 -0
- package/dist/index.mjs.map +1 -0
- package/dist/paths-D_JlVmBk.mjs +42 -0
- package/dist/paths-D_JlVmBk.mjs.map +1 -0
- package/dist/script.d.mts +193 -0
- package/dist/script.mjs +782 -0
- package/dist/script.mjs.map +1 -0
- package/dist/subagent.d.mts +336 -0
- package/dist/subagent.mjs +1402 -0
- package/dist/subagent.mjs.map +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +106 -3
- package/static/icon.png +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Synthesizer Prompt Template
|
|
2
|
+
|
|
3
|
+
Fill in the placeholders.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are answering a "why" question about a piece of code by synthesizing findings from investigators who searched different historical sources (source control, issue / ticket tracker, long-form documents, real-time team chat, infrastructure observability, error / exception tracking, product analytics warehouse, and code comments). Produce a confidence-weighted, evidence-cited narrative that says honestly what the evidence supports, and just as honestly what it doesn't.
|
|
8
|
+
|
|
9
|
+
## The Question
|
|
10
|
+
|
|
11
|
+
> {QUESTION}
|
|
12
|
+
|
|
13
|
+
## The Code Anchor
|
|
14
|
+
|
|
15
|
+
**Target files:** {FILES_WITH_LINE_RANGES}
|
|
16
|
+
|
|
17
|
+
**Key symbols:** {SYMBOLS}
|
|
18
|
+
|
|
19
|
+
## Investigator Findings
|
|
20
|
+
|
|
21
|
+
{ALL_INVESTIGATOR_FINDINGS}
|
|
22
|
+
|
|
23
|
+
## Sources That Weren't Searched
|
|
24
|
+
|
|
25
|
+
{SKIPPED_SOURCES_WITH_REASONS}
|
|
26
|
+
|
|
27
|
+
## Epistemics Framework
|
|
28
|
+
|
|
29
|
+
You MUST follow `references/epistemics.md`. Read it in full before writing. The key rules:
|
|
30
|
+
|
|
31
|
+
1. Every claim must sit in one tier: **Direct**, **Supported**, **Inferred**, **Speculative**, **Unknown**. The tier sets its section and its phrasing.
|
|
32
|
+
2. Every Direct/Supported claim must have a citation (PR #, ticket ID, doc URL, chat permalink, commit hash, or file:line).
|
|
33
|
+
3. Inferred and Speculative claims must use hedged language ("appears to", "likely", "suggests", "one possibility is").
|
|
34
|
+
4. Never cite code as evidence for its own intent.
|
|
35
|
+
5. Document gaps. Don't fill them with plausible-sounding guesses.
|
|
36
|
+
6. Treat a hypothesis embedded in the user's question as a candidate, not a conclusion. Check it independently.
|
|
37
|
+
|
|
38
|
+
## Instructions
|
|
39
|
+
|
|
40
|
+
1. **Read all investigator findings** and weigh the evidence yourself. Investigators gathered raw evidence, not conclusions.
|
|
41
|
+
2. **Merge overlaps.** When several investigators cite the same PR, ticket, or doc, give it one authoritative reference.
|
|
42
|
+
3. **Surface contradictions.** When two items disagree, show both. Don't pick one.
|
|
43
|
+
4. **Calibrate each claim.** Find its evidence and tier. Direct: cite it and state it plainly. Inferred: hedge it and show the inference. Speculative: mark it. No evidence: put it in the gaps.
|
|
44
|
+
5. **Verify citations by spot-checking.** If you're uncertain a cited item exists or says what the investigator claims, check it by reading the codebase or calling MCP tools. Don't propagate errors. Do not write files, commit, or modify external state.
|
|
45
|
+
6. **Don't overreach.** The user will act on this. Leave an open question open rather than fill it with a confident guess.
|
|
46
|
+
|
|
47
|
+
## Output Format
|
|
48
|
+
|
|
49
|
+
Write for the user, in this exact structure:
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### The Question
|
|
54
|
+
|
|
55
|
+
Restate the user's question in one or two sentences.
|
|
56
|
+
|
|
57
|
+
### The Code in Question
|
|
58
|
+
|
|
59
|
+
File paths, line ranges, key symbols. Two or three lines to orient a cold reader.
|
|
60
|
+
|
|
61
|
+
### What We Found
|
|
62
|
+
|
|
63
|
+
Claims with textual evidence, each quoted or paraphrased and cited precisely:
|
|
64
|
+
|
|
65
|
+
- **[Direct]** {Claim}. Source: [PR #123](url) / ticket ID / file:line. {Brief quote or paraphrase.}
|
|
66
|
+
- **[Supported]** {Claim}. Evidence: {list of items and what each contributes}.
|
|
67
|
+
|
|
68
|
+
`[Direct]` is single-source, explicit evidence. `[Supported]` is multiple indirect items converging on a conclusion.
|
|
69
|
+
|
|
70
|
+
### What We Can Reasonably Infer
|
|
71
|
+
|
|
72
|
+
Claims stated nowhere but well-supported by indirect evidence. Each bullet must make the inference chain visible ("Given A and B, it's likely that C") and must use hedged language ("appears to", "likely", "suggests", "is consistent with"):
|
|
73
|
+
|
|
74
|
+
- **[Inferred]** {Hedged claim}. Reasoning: {the specific evidence and the inference step}.
|
|
75
|
+
|
|
76
|
+
Skip this section if there's nothing to infer.
|
|
77
|
+
|
|
78
|
+
### Competing Hypotheses
|
|
79
|
+
|
|
80
|
+
If the evidence fits multiple stories, present each. Don't force a winner the record doesn't support. For each hypothesis:
|
|
81
|
+
|
|
82
|
+
- **Hypothesis:** {one-sentence statement}
|
|
83
|
+
- **Evidence for:** {specific items}
|
|
84
|
+
- **Evidence against or missing:** {what would need to be true but isn't, or what counter-signals exist}
|
|
85
|
+
|
|
86
|
+
Skip this section if there's a single clear answer.
|
|
87
|
+
|
|
88
|
+
### What We Don't Know
|
|
89
|
+
|
|
90
|
+
Specific gaps: "We searched the issue tracker for [query1], [query2], [query3] and found no issue discussing the rate-limit threshold", not "We don't know why." Include:
|
|
91
|
+
|
|
92
|
+
- Questions that went unanswered
|
|
93
|
+
- Searches that returned nothing
|
|
94
|
+
- Sources that were unavailable, and why (such as a missing real-time team chat MCP)
|
|
95
|
+
- People who would likely know but whom you can't ask
|
|
96
|
+
|
|
97
|
+
### Sources Consulted
|
|
98
|
+
|
|
99
|
+
What was actually searched, so the user can judge coverage and redirect:
|
|
100
|
+
|
|
101
|
+
- **Source control history**: {file paths}, {number of commits reviewed}, PRs #{numbers}, and code comments searched. Or "Not searched. This should not happen because git and `gh` are always expected."
|
|
102
|
+
- **Issue / ticket tracker**: {ticket IDs and keyword searches}. Or "Not searched. No matching MCP available in this environment."
|
|
103
|
+
- **Long-form documents**: {page titles and search queries}. Or "Not searched. No matching MCP available in this environment."
|
|
104
|
+
- **Real-time team chat**: {channels searched, date ranges, queries}. Or "Not searched. No matching MCP available in this environment."
|
|
105
|
+
- **Infrastructure observability**: {dashboards, monitors, metrics, logs, traces, or incidents searched}. Or "Not searched. No matching MCP available in this environment."
|
|
106
|
+
- **Error / exception tracking**: {issues, events, or releases searched}. Or "Not searched. No matching MCP available in this environment."
|
|
107
|
+
- **Product analytics warehouse**: {fully-qualified tables queried, the time windows, and the numeric summaries (counts, percentiles, first/last-seen timestamps) that bore on the question}. Or "Not searched. No matching MCP available in this environment."
|
|
108
|
+
|
|
109
|
+
### Confidence Summary
|
|
110
|
+
|
|
111
|
+
One or two sentences on overall confidence, e.g.:
|
|
112
|
+
|
|
113
|
+
> "The core rationale (A) is well-supported by direct PR and ticket evidence. The specific threshold value (100) is inferred from the surrounding context but not explicitly documented. The question of whether this was driven by a customer request could not be answered. No relevant issue tracker or long-form doc content surfaced, and real-time team chat search was unavailable."
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Quality Check Before Returning
|
|
118
|
+
|
|
119
|
+
Revise if any item fails:
|
|
120
|
+
|
|
121
|
+
1. Does every claim in "What We Found" have a citation? If not, add one or move it to "Inferred" or "Hypotheses."
|
|
122
|
+
2. Is the phrasing tier-appropriate? (Direct claims can use "because". Inferred claims cannot.)
|
|
123
|
+
3. Did you surface every contradiction you noticed, or quietly pick one?
|
|
124
|
+
4. Does "What We Don't Know" name specific gaps? If it's empty or missing, be suspicious. Historical investigations almost always have gaps.
|
|
125
|
+
5. Did you check any hypothesis embedded in the question against the evidence rather than rubber-stamp it?
|
|
126
|
+
6. Did you cite code as evidence for its own intent? Remove it. Code is mechanics, not motivation.
|
|
127
|
+
7. Is the tone calibrated? A confident-sounding answer on weak evidence is the exact failure this skill exists to prevent.
|
|
128
|
+
|
|
129
|
+
The value of this output is its honesty, not its authority. A reader who takes it to the original author, an engineering lead, or a product manager should know what's known, what's inferred, and what's missing, so they can ask the right follow-up questions. Optimize for being useful, not for looking decisive.
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# dsh-pstack bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# `pstack` registers the bundled skill provider. `pstack-subagent` registers
|
|
4
|
+
# `pstack_subagent`, which starts one child per activation and re-reads the role
|
|
5
|
+
# file on every delegation. `pstack-history` registers `pstack_history`, the
|
|
6
|
+
# restricted read-only history tool; it reads raw events through `sessionQuery`
|
|
7
|
+
# and authorizes the caller and its direct children only.
|
|
8
|
+
#
|
|
9
|
+
# The two worktree rows carry the README promise that worktree support ships as a
|
|
10
|
+
# necessary dependency: the bundle turns the official integration on when the
|
|
11
|
+
# profile does not have it, and mounts nothing when it does. `disabled: !!js` is
|
|
12
|
+
# evaluated by the Loader at every mount decision, and each expression reads the
|
|
13
|
+
# composed row list rather than a startup snapshot. The row handle is
|
|
14
|
+
# `ctx[Symbol.for('cordis.entry')]` (this entry), not `ctx.fiber.entry`, which
|
|
15
|
+
# resolves to the enclosing `include` row and therefore never sees these siblings.
|
|
16
|
+
# `scripts/worktree-mount.ts` owns the text of both expressions and `check:assets`
|
|
17
|
+
# fails when this file drifts from it. See
|
|
18
|
+
# docs/decisions/2026-10-26-worktree-guard-reads-own-entry.md.
|
|
19
|
+
#
|
|
20
|
+
# `pstack-history` is appended after the unit-4 rows so no delivered line moves.
|
|
21
|
+
# Row order is not part of the history contract; the two worktree guards, by
|
|
22
|
+
# contrast, stay adjacent and byte-identical to their generator.
|
|
23
|
+
- insert:
|
|
24
|
+
- id: pstack
|
|
25
|
+
name: dsh-pstack
|
|
26
|
+
- id: pstack-subagent
|
|
27
|
+
name: dsh-pstack/subagent
|
|
28
|
+
config:
|
|
29
|
+
maxDepth: 2
|
|
30
|
+
- id: pstack-worktree
|
|
31
|
+
name: '@deepseek-ai/dsh-experimental-worktree'
|
|
32
|
+
disabled: !!js "(() => { const own = ctx[Symbol.for('cordis.entry')]; const rows = own && own.parent && own.parent.data; if (!Array.isArray(rows)) return false; return rows.some((row) => row !== null && typeof row === 'object' && row.id !== 'pstack-worktree' && row.name === '@deepseek-ai/dsh-experimental-worktree' && row.disabled !== true) })()"
|
|
33
|
+
- id: pstack-tool-worktree
|
|
34
|
+
name: '@deepseek-ai/dsh-experimental-tool-worktree'
|
|
35
|
+
disabled: !!js "(() => { const own = ctx[Symbol.for('cordis.entry')]; const rows = own && own.parent && own.parent.data; if (!Array.isArray(rows)) return false; return rows.some((row) => row !== null && typeof row === 'object' && row.id !== 'pstack-tool-worktree' && row.name === '@deepseek-ai/dsh-experimental-tool-worktree' && row.disabled !== true) })()"
|
|
36
|
+
- id: pstack-history
|
|
37
|
+
name: dsh-pstack/history
|
|
38
|
+
- id: pstack-script
|
|
39
|
+
name: dsh-pstack/script
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
//#region src/frontmatter.ts
|
|
2
|
+
const SKILL_CONSUMED_FIELDS = [
|
|
3
|
+
"name",
|
|
4
|
+
"description",
|
|
5
|
+
"disable-model-invocation",
|
|
6
|
+
"user-invocable",
|
|
7
|
+
"whenToUse"
|
|
8
|
+
];
|
|
9
|
+
const SKILL_DROPPED_FIELDS = [
|
|
10
|
+
"icon",
|
|
11
|
+
"color",
|
|
12
|
+
"paths"
|
|
13
|
+
];
|
|
14
|
+
const AGENT_CONSUMED_FIELDS = ["name", "description"];
|
|
15
|
+
const AGENT_DROPPED_FIELDS = ["is_background"];
|
|
16
|
+
const MAX_SKILL_NAME_LENGTH = 64;
|
|
17
|
+
const SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
18
|
+
var FrontmatterError = class extends Error {
|
|
19
|
+
constructor(message) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "FrontmatterError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
function parseFrontmatter(source) {
|
|
25
|
+
const eol = detectEol(source);
|
|
26
|
+
const lines = (eol === "\r\n" ? source.replaceAll("\r\n", "\n") : source).split("\n");
|
|
27
|
+
if (lines[0] !== "---") throw new FrontmatterError("frontmatter: file must start with a \"---\" line");
|
|
28
|
+
const closing = lines.indexOf("---", 1);
|
|
29
|
+
if (closing === -1) throw new FrontmatterError("frontmatter: missing closing \"---\" delimiter");
|
|
30
|
+
const data = parseBlock(lines.slice(1, closing));
|
|
31
|
+
const body = lines.slice(closing + 1).join("\n");
|
|
32
|
+
return {
|
|
33
|
+
data,
|
|
34
|
+
body: eol === "\r\n" ? body.replaceAll("\n", eol) : body
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function validateSkillFrontmatter(data, source) {
|
|
38
|
+
rejectUnknownFields(data, source, SKILL_CONSUMED_FIELDS, SKILL_DROPPED_FIELDS);
|
|
39
|
+
const name = readName(data, source);
|
|
40
|
+
const description = readDescription(data, source);
|
|
41
|
+
const whenToUse = data["whenToUse"];
|
|
42
|
+
if (whenToUse !== void 0 && whenToUse.trim() === "") throw new FrontmatterError(`${source}: "whenToUse" must not be empty`);
|
|
43
|
+
return {
|
|
44
|
+
name,
|
|
45
|
+
description,
|
|
46
|
+
disableModelInvocation: readBoolean(data, "disable-model-invocation", source, false),
|
|
47
|
+
userInvocable: readBoolean(data, "user-invocable", source, true),
|
|
48
|
+
...whenToUse === void 0 ? {} : { whenToUse }
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function validateAgentFrontmatter(data, source) {
|
|
52
|
+
rejectUnknownFields(data, source, AGENT_CONSUMED_FIELDS, AGENT_DROPPED_FIELDS);
|
|
53
|
+
return {
|
|
54
|
+
name: readName(data, source),
|
|
55
|
+
description: readDescription(data, source)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function serializeSkillFrontmatter(data) {
|
|
59
|
+
const lines = [
|
|
60
|
+
"---",
|
|
61
|
+
`name: ${formatScalar(data.name)}`,
|
|
62
|
+
`description: ${formatScalar(data.description)}`
|
|
63
|
+
];
|
|
64
|
+
if (data.disableModelInvocation) lines.push("disable-model-invocation: true");
|
|
65
|
+
if (!data.userInvocable) lines.push("user-invocable: false");
|
|
66
|
+
if (data.whenToUse !== void 0) lines.push(`whenToUse: ${formatScalar(data.whenToUse)}`);
|
|
67
|
+
lines.push("---");
|
|
68
|
+
return `${lines.join("\n")}\n`;
|
|
69
|
+
}
|
|
70
|
+
function serializeAgentFrontmatter(data) {
|
|
71
|
+
return [
|
|
72
|
+
"---",
|
|
73
|
+
`name: ${formatScalar(data.name)}`,
|
|
74
|
+
`description: ${formatScalar(data.description)}`,
|
|
75
|
+
"---",
|
|
76
|
+
""
|
|
77
|
+
].join("\n");
|
|
78
|
+
}
|
|
79
|
+
function skillDroppedFields(data, source) {
|
|
80
|
+
return droppedFields(data, source, SKILL_DROPPED_FIELDS);
|
|
81
|
+
}
|
|
82
|
+
function agentDroppedFields(data, source) {
|
|
83
|
+
return droppedFields(data, source, AGENT_DROPPED_FIELDS);
|
|
84
|
+
}
|
|
85
|
+
function droppedFields(data, source, fields) {
|
|
86
|
+
return fields.flatMap((field) => {
|
|
87
|
+
const value = data[field];
|
|
88
|
+
return value === void 0 ? [] : [{
|
|
89
|
+
file: source,
|
|
90
|
+
field,
|
|
91
|
+
value
|
|
92
|
+
}];
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function detectEol(source) {
|
|
96
|
+
if (!source.includes("\r\n")) return "\n";
|
|
97
|
+
const withoutCrlf = source.replaceAll("\r\n", "");
|
|
98
|
+
if (withoutCrlf.includes("\r") || withoutCrlf.includes("\n")) throw new FrontmatterError("frontmatter: mixed line endings are not supported");
|
|
99
|
+
return "\r\n";
|
|
100
|
+
}
|
|
101
|
+
function parseBlock(lines) {
|
|
102
|
+
const data = {};
|
|
103
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
104
|
+
const line = lines[index];
|
|
105
|
+
const trimmed = line.trim();
|
|
106
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
107
|
+
if (trimmed !== line) throw new FrontmatterError(`frontmatter: unexpected indented line "${trimmed}"`);
|
|
108
|
+
const entry = /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/.exec(line);
|
|
109
|
+
if (entry === null) throw new FrontmatterError(`frontmatter: expected "key: value", got "${line}"`);
|
|
110
|
+
const key = entry[1];
|
|
111
|
+
const rest = entry[2];
|
|
112
|
+
if (rest !== "" && !rest.startsWith(" ")) throw new FrontmatterError(`frontmatter: missing space after "${key}:"`);
|
|
113
|
+
if (Object.hasOwn(data, key)) throw new FrontmatterError(`frontmatter: duplicate key "${key}"`);
|
|
114
|
+
const raw = rest.trim();
|
|
115
|
+
if (raw === ">" || raw === ">-" || raw === "|" || raw === "|-") {
|
|
116
|
+
const block = [];
|
|
117
|
+
let cursor = index + 1;
|
|
118
|
+
while (cursor < lines.length) {
|
|
119
|
+
const candidate = lines[cursor];
|
|
120
|
+
if (candidate.trim() !== "" && !candidate.startsWith(" ") && !candidate.startsWith(" ")) break;
|
|
121
|
+
block.push(candidate);
|
|
122
|
+
cursor += 1;
|
|
123
|
+
}
|
|
124
|
+
while (block.length > 0 && block.at(-1).trim() === "") block.pop();
|
|
125
|
+
if (block.length === 0) throw new FrontmatterError(`frontmatter: "${key}" has an empty ${raw} block`);
|
|
126
|
+
data[key] = parseBlockScalar(block, raw, key);
|
|
127
|
+
index = cursor - 1;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
data[key] = parseScalar(raw, key);
|
|
131
|
+
}
|
|
132
|
+
return data;
|
|
133
|
+
}
|
|
134
|
+
function parseScalar(raw, key) {
|
|
135
|
+
if (raw === "") return "";
|
|
136
|
+
if (raw.startsWith("\"")) return parseDoubleQuoted(raw, key);
|
|
137
|
+
if (raw.startsWith("'")) return parseSingleQuoted(raw, key);
|
|
138
|
+
return raw;
|
|
139
|
+
}
|
|
140
|
+
function parseDoubleQuoted(raw, key) {
|
|
141
|
+
if (raw.length < 2 || !raw.endsWith("\"")) throw new FrontmatterError(`frontmatter: unterminated double-quoted value for "${key}"`);
|
|
142
|
+
const inner = raw.slice(1, -1);
|
|
143
|
+
let value = "";
|
|
144
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
145
|
+
const character = inner[index];
|
|
146
|
+
if (character !== "\\") {
|
|
147
|
+
value += character;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const escaped = inner[index += 1];
|
|
151
|
+
if (escaped === "\"") value += "\"";
|
|
152
|
+
else if (escaped === "\\") value += "\\";
|
|
153
|
+
else if (escaped === "n") value += "\n";
|
|
154
|
+
else throw new FrontmatterError(`frontmatter: unsupported escape "\\${escaped ?? ""}" in "${key}"`);
|
|
155
|
+
}
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
function parseSingleQuoted(raw, key) {
|
|
159
|
+
if (raw.length < 2 || !raw.endsWith("'")) throw new FrontmatterError(`frontmatter: unterminated single-quoted value for "${key}"`);
|
|
160
|
+
const inner = raw.slice(1, -1);
|
|
161
|
+
let value = "";
|
|
162
|
+
for (let index = 0; index < inner.length; index += 1) {
|
|
163
|
+
const character = inner[index];
|
|
164
|
+
if (character !== "'") {
|
|
165
|
+
value += character;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (inner[index + 1] === "'") {
|
|
169
|
+
value += "'";
|
|
170
|
+
index += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
throw new FrontmatterError(`frontmatter: unescaped single quote in "${key}"`);
|
|
174
|
+
}
|
|
175
|
+
return value;
|
|
176
|
+
}
|
|
177
|
+
function parseBlockScalar(lines, indicator, key) {
|
|
178
|
+
const indents = lines.filter((line) => line.trim() !== "").map((line) => /^\s*/.exec(line)[0].length);
|
|
179
|
+
const indent = Math.min(...indents);
|
|
180
|
+
const content = lines.map((line) => line.trim() === "" ? "" : line.slice(indent));
|
|
181
|
+
const text = indicator.startsWith("|") ? content.join("\n") : fold(content);
|
|
182
|
+
if (text.trim() === "") throw new FrontmatterError(`frontmatter: "${key}" block is empty`);
|
|
183
|
+
return indicator.endsWith("-") ? text : `${text}\n`;
|
|
184
|
+
}
|
|
185
|
+
function fold(lines) {
|
|
186
|
+
const paragraphs = [[]];
|
|
187
|
+
for (const line of lines) if (line === "") paragraphs.push([]);
|
|
188
|
+
else paragraphs.at(-1).push(line);
|
|
189
|
+
return paragraphs.map((paragraph) => paragraph.join(" ")).join("\n");
|
|
190
|
+
}
|
|
191
|
+
function rejectUnknownFields(data, source, consumed, dropped) {
|
|
192
|
+
const unknown = Object.keys(data).filter((key) => !consumed.includes(key) && !dropped.includes(key));
|
|
193
|
+
if (unknown.length > 0) throw new FrontmatterError(`${source}: unknown frontmatter field(s) ${unknown.join(", ")}`);
|
|
194
|
+
}
|
|
195
|
+
function readName(data, source) {
|
|
196
|
+
const value = data["name"];
|
|
197
|
+
if (value === void 0 || value.trim() === "") throw new FrontmatterError(`${source}: missing "name"`);
|
|
198
|
+
if (!SKILL_NAME_PATTERN.test(value)) throw new FrontmatterError(`${source}: "name" must be kebab-case, got "${value}"`);
|
|
199
|
+
if (value.length > 64) throw new FrontmatterError(`${source}: "name" is longer than 64 characters`);
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
function readDescription(data, source) {
|
|
203
|
+
const value = data["description"];
|
|
204
|
+
if (value === void 0 || value.trim() === "") throw new FrontmatterError(`${source}: missing "description"`);
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
function readBoolean(data, key, source, fallback) {
|
|
208
|
+
const value = data[key];
|
|
209
|
+
if (value === void 0) return fallback;
|
|
210
|
+
if (value === "true") return true;
|
|
211
|
+
if (value === "false") return false;
|
|
212
|
+
throw new FrontmatterError(`${source}: "${key}" must be true or false, got "${value}"`);
|
|
213
|
+
}
|
|
214
|
+
function formatScalar(value) {
|
|
215
|
+
return needsQuotes(value) ? `"${escapeDoubleQuoted(value)}"` : value;
|
|
216
|
+
}
|
|
217
|
+
function needsQuotes(value) {
|
|
218
|
+
if (value === "" || value.trim() !== value) return true;
|
|
219
|
+
if (/[\n\r\t]/.test(value)) return true;
|
|
220
|
+
if (/^[-?:,[\]{}#&*!|>'"%@`]/.test(value)) return true;
|
|
221
|
+
return /: | #|:$/.test(value);
|
|
222
|
+
}
|
|
223
|
+
function escapeDoubleQuoted(value) {
|
|
224
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n").replaceAll("\r", "\\r");
|
|
225
|
+
}
|
|
226
|
+
//#endregion
|
|
227
|
+
export { SKILL_CONSUMED_FIELDS as a, parseFrontmatter as c, skillDroppedFields as d, validateAgentFrontmatter as f, MAX_SKILL_NAME_LENGTH as i, serializeAgentFrontmatter as l, AGENT_DROPPED_FIELDS as n, SKILL_DROPPED_FIELDS as o, validateSkillFrontmatter as p, FrontmatterError as r, agentDroppedFields as s, AGENT_CONSUMED_FIELDS as t, serializeSkillFrontmatter as u };
|
|
228
|
+
|
|
229
|
+
//# sourceMappingURL=frontmatter-BQX3ekwP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatter-BQX3ekwP.mjs","names":[],"sources":["../src/frontmatter.ts"],"sourcesContent":["/**\n * Frontmatter parsing for pstack skills and agents.\n *\n * This is a deliberately small YAML subset, not YAML. It reads the shapes the\n * upstream files use: plain scalars, single and double quotes, folded (`>` and\n * `>-`) and literal (`|` and `|-`) blocks, comments, and blank lines. Nesting\n * and multi-document streams are rejected rather than half-supported.\n */\n\nexport interface Frontmatter {\n readonly data: Readonly<Record<string, string>>;\n readonly body: string;\n}\n\nexport interface SkillFrontmatter {\n readonly name: string;\n readonly description: string;\n readonly disableModelInvocation: boolean;\n readonly userInvocable: boolean;\n readonly whenToUse?: string;\n}\n\nexport interface AgentFrontmatter {\n readonly name: string;\n readonly description: string;\n}\n\nexport interface DroppedFrontmatterField {\n readonly file: string;\n readonly field: string;\n readonly value: string;\n}\n\nexport const SKILL_CONSUMED_FIELDS = [\n \"name\",\n \"description\",\n \"disable-model-invocation\",\n \"user-invocable\",\n \"whenToUse\",\n] as const;\n\nexport const SKILL_DROPPED_FIELDS = [\"icon\", \"color\", \"paths\"] as const;\n\nexport const AGENT_CONSUMED_FIELDS = [\"name\", \"description\"] as const;\n\nexport const AGENT_DROPPED_FIELDS = [\"is_background\"] as const;\n\nexport const MAX_SKILL_NAME_LENGTH = 64;\n\nconst SKILL_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\nexport class FrontmatterError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"FrontmatterError\";\n }\n}\n\nexport function parseFrontmatter(source: string): Frontmatter {\n const eol = detectEol(source);\n const text = eol === \"\\r\\n\" ? source.replaceAll(\"\\r\\n\", \"\\n\") : source;\n const lines = text.split(\"\\n\");\n\n if (lines[0] !== \"---\") {\n throw new FrontmatterError('frontmatter: file must start with a \"---\" line');\n }\n const closing = lines.indexOf(\"---\", 1);\n if (closing === -1) {\n throw new FrontmatterError('frontmatter: missing closing \"---\" delimiter');\n }\n\n const data = parseBlock(lines.slice(1, closing));\n const body = lines.slice(closing + 1).join(\"\\n\");\n return { data, body: eol === \"\\r\\n\" ? body.replaceAll(\"\\n\", eol) : body };\n}\n\nexport function validateSkillFrontmatter(\n data: Readonly<Record<string, string>>,\n source: string,\n): SkillFrontmatter {\n rejectUnknownFields(data, source, SKILL_CONSUMED_FIELDS, SKILL_DROPPED_FIELDS);\n\n const name = readName(data, source);\n const description = readDescription(data, source);\n const whenToUse = data[\"whenToUse\"];\n if (whenToUse !== undefined && whenToUse.trim() === \"\") {\n throw new FrontmatterError(`${source}: \"whenToUse\" must not be empty`);\n }\n\n return {\n name,\n description,\n disableModelInvocation: readBoolean(data, \"disable-model-invocation\", source, false),\n userInvocable: readBoolean(data, \"user-invocable\", source, true),\n ...(whenToUse === undefined ? {} : { whenToUse }),\n };\n}\n\nexport function validateAgentFrontmatter(\n data: Readonly<Record<string, string>>,\n source: string,\n): AgentFrontmatter {\n rejectUnknownFields(data, source, AGENT_CONSUMED_FIELDS, AGENT_DROPPED_FIELDS);\n return { name: readName(data, source), description: readDescription(data, source) };\n}\n\nexport function serializeSkillFrontmatter(data: SkillFrontmatter): string {\n const lines = [\n \"---\",\n `name: ${formatScalar(data.name)}`,\n `description: ${formatScalar(data.description)}`,\n ];\n if (data.disableModelInvocation) lines.push(\"disable-model-invocation: true\");\n if (!data.userInvocable) lines.push(\"user-invocable: false\");\n if (data.whenToUse !== undefined) lines.push(`whenToUse: ${formatScalar(data.whenToUse)}`);\n lines.push(\"---\");\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function serializeAgentFrontmatter(data: AgentFrontmatter): string {\n return [\n \"---\",\n `name: ${formatScalar(data.name)}`,\n `description: ${formatScalar(data.description)}`,\n \"---\",\n \"\",\n ].join(\"\\n\");\n}\n\nexport function skillDroppedFields(\n data: Readonly<Record<string, string>>,\n source: string,\n): readonly DroppedFrontmatterField[] {\n return droppedFields(data, source, SKILL_DROPPED_FIELDS);\n}\n\nexport function agentDroppedFields(\n data: Readonly<Record<string, string>>,\n source: string,\n): readonly DroppedFrontmatterField[] {\n return droppedFields(data, source, AGENT_DROPPED_FIELDS);\n}\n\nfunction droppedFields(\n data: Readonly<Record<string, string>>,\n source: string,\n fields: readonly string[],\n): readonly DroppedFrontmatterField[] {\n return fields.flatMap((field) => {\n const value = data[field];\n return value === undefined ? [] : [{ file: source, field, value }];\n });\n}\n\nfunction detectEol(source: string): \"\\n\" | \"\\r\\n\" {\n if (!source.includes(\"\\r\\n\")) return \"\\n\";\n const withoutCrlf = source.replaceAll(\"\\r\\n\", \"\");\n if (withoutCrlf.includes(\"\\r\") || withoutCrlf.includes(\"\\n\")) {\n throw new FrontmatterError(\"frontmatter: mixed line endings are not supported\");\n }\n return \"\\r\\n\";\n}\n\nfunction parseBlock(lines: readonly string[]): Record<string, string> {\n const data: Record<string, string> = {};\n\n for (let index = 0; index < lines.length; index += 1) {\n const line = lines[index] as string;\n const trimmed = line.trim();\n if (trimmed === \"\" || trimmed.startsWith(\"#\")) continue;\n if (trimmed !== line) {\n throw new FrontmatterError(`frontmatter: unexpected indented line \"${trimmed}\"`);\n }\n\n const entry = /^([A-Za-z_][A-Za-z0-9_-]*):(.*)$/.exec(line);\n if (entry === null) {\n throw new FrontmatterError(`frontmatter: expected \"key: value\", got \"${line}\"`);\n }\n const key = entry[1] as string;\n const rest = entry[2] as string;\n if (rest !== \"\" && !rest.startsWith(\" \")) {\n throw new FrontmatterError(`frontmatter: missing space after \"${key}:\"`);\n }\n if (Object.hasOwn(data, key)) {\n throw new FrontmatterError(`frontmatter: duplicate key \"${key}\"`);\n }\n\n const raw = rest.trim();\n if (raw === \">\" || raw === \">-\" || raw === \"|\" || raw === \"|-\") {\n const block: string[] = [];\n let cursor = index + 1;\n while (cursor < lines.length) {\n const candidate = lines[cursor] as string;\n if (candidate.trim() !== \"\" && !candidate.startsWith(\" \") && !candidate.startsWith(\"\\t\"))\n break;\n block.push(candidate);\n cursor += 1;\n }\n while (block.length > 0 && (block.at(-1) as string).trim() === \"\") block.pop();\n if (block.length === 0) {\n throw new FrontmatterError(`frontmatter: \"${key}\" has an empty ${raw} block`);\n }\n data[key] = parseBlockScalar(block, raw, key);\n index = cursor - 1;\n continue;\n }\n\n data[key] = parseScalar(raw, key);\n }\n\n return data;\n}\n\nfunction parseScalar(raw: string, key: string): string {\n if (raw === \"\") return \"\";\n if (raw.startsWith('\"')) return parseDoubleQuoted(raw, key);\n if (raw.startsWith(\"'\")) return parseSingleQuoted(raw, key);\n return raw;\n}\n\nfunction parseDoubleQuoted(raw: string, key: string): string {\n if (raw.length < 2 || !raw.endsWith('\"')) {\n throw new FrontmatterError(`frontmatter: unterminated double-quoted value for \"${key}\"`);\n }\n const inner = raw.slice(1, -1);\n let value = \"\";\n for (let index = 0; index < inner.length; index += 1) {\n const character = inner[index] as string;\n if (character !== \"\\\\\") {\n value += character;\n continue;\n }\n const escaped = inner[(index += 1)];\n if (escaped === '\"') value += '\"';\n else if (escaped === \"\\\\\") value += \"\\\\\";\n else if (escaped === \"n\") value += \"\\n\";\n else\n throw new FrontmatterError(\n `frontmatter: unsupported escape \"\\\\${escaped ?? \"\"}\" in \"${key}\"`,\n );\n }\n return value;\n}\n\nfunction parseSingleQuoted(raw: string, key: string): string {\n if (raw.length < 2 || !raw.endsWith(\"'\")) {\n throw new FrontmatterError(`frontmatter: unterminated single-quoted value for \"${key}\"`);\n }\n const inner = raw.slice(1, -1);\n let value = \"\";\n for (let index = 0; index < inner.length; index += 1) {\n const character = inner[index] as string;\n if (character !== \"'\") {\n value += character;\n continue;\n }\n if (inner[index + 1] === \"'\") {\n value += \"'\";\n index += 1;\n continue;\n }\n throw new FrontmatterError(`frontmatter: unescaped single quote in \"${key}\"`);\n }\n return value;\n}\n\nfunction parseBlockScalar(lines: readonly string[], indicator: string, key: string): string {\n const indents = lines\n .filter((line) => line.trim() !== \"\")\n .map((line) => (/^\\s*/.exec(line) as RegExpExecArray)[0].length);\n const indent = Math.min(...indents);\n const content = lines.map((line) => (line.trim() === \"\" ? \"\" : line.slice(indent)));\n\n const text = indicator.startsWith(\"|\") ? content.join(\"\\n\") : fold(content);\n if (text.trim() === \"\") {\n throw new FrontmatterError(`frontmatter: \"${key}\" block is empty`);\n }\n return indicator.endsWith(\"-\") ? text : `${text}\\n`;\n}\n\nfunction fold(lines: readonly string[]): string {\n const paragraphs: string[][] = [[]];\n for (const line of lines) {\n if (line === \"\") paragraphs.push([]);\n else (paragraphs.at(-1) as string[]).push(line);\n }\n return paragraphs.map((paragraph) => paragraph.join(\" \")).join(\"\\n\");\n}\n\nfunction rejectUnknownFields(\n data: Readonly<Record<string, string>>,\n source: string,\n consumed: readonly string[],\n dropped: readonly string[],\n): void {\n const unknown = Object.keys(data).filter(\n (key) => !consumed.includes(key) && !dropped.includes(key),\n );\n if (unknown.length > 0) {\n throw new FrontmatterError(`${source}: unknown frontmatter field(s) ${unknown.join(\", \")}`);\n }\n}\n\nfunction readName(data: Readonly<Record<string, string>>, source: string): string {\n const value = data[\"name\"];\n if (value === undefined || value.trim() === \"\") {\n throw new FrontmatterError(`${source}: missing \"name\"`);\n }\n if (!SKILL_NAME_PATTERN.test(value)) {\n throw new FrontmatterError(`${source}: \"name\" must be kebab-case, got \"${value}\"`);\n }\n if (value.length > MAX_SKILL_NAME_LENGTH) {\n throw new FrontmatterError(\n `${source}: \"name\" is longer than ${MAX_SKILL_NAME_LENGTH} characters`,\n );\n }\n return value;\n}\n\nfunction readDescription(data: Readonly<Record<string, string>>, source: string): string {\n const value = data[\"description\"];\n if (value === undefined || value.trim() === \"\") {\n throw new FrontmatterError(`${source}: missing \"description\"`);\n }\n return value;\n}\n\nfunction readBoolean(\n data: Readonly<Record<string, string>>,\n key: string,\n source: string,\n fallback: boolean,\n): boolean {\n const value = data[key];\n if (value === undefined) return fallback;\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n throw new FrontmatterError(`${source}: \"${key}\" must be true or false, got \"${value}\"`);\n}\n\nfunction formatScalar(value: string): string {\n return needsQuotes(value) ? `\"${escapeDoubleQuoted(value)}\"` : value;\n}\n\nfunction needsQuotes(value: string): boolean {\n if (value === \"\" || value.trim() !== value) return true;\n if (/[\\n\\r\\t]/.test(value)) return true;\n if (/^[-?:,[\\]{}#&*!|>'\"%@`]/.test(value)) return true;\n return /: | #|:$/.test(value);\n}\n\nfunction escapeDoubleQuoted(value: string): string {\n return value\n .replaceAll(\"\\\\\", \"\\\\\\\\\")\n .replaceAll('\"', '\\\\\"')\n .replaceAll(\"\\n\", \"\\\\n\")\n .replaceAll(\"\\r\", \"\\\\r\");\n}\n"],"mappings":"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"}
|