@sous-io/sous 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.
Files changed (82) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +154 -0
  3. package/bin/run.js +17 -0
  4. package/bin/xcv +5 -0
  5. package/package.json +81 -0
  6. package/shared-prompts/_partials/resume-task.md +51 -0
  7. package/shared-prompts/_partials/sub-agent-delegation.md +32 -0
  8. package/shared-prompts/_partials/update-task-file.md +52 -0
  9. package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +52 -0
  10. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +102 -0
  11. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +81 -0
  12. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +126 -0
  13. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +92 -0
  14. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +61 -0
  15. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +65 -0
  16. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +96 -0
  17. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +104 -0
  18. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +243 -0
  19. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +148 -0
  20. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +383 -0
  21. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +267 -0
  22. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +56 -0
  23. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +169 -0
  24. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +59 -0
  25. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +25 -0
  26. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +140 -0
  27. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +140 -0
  28. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +1 -0
  29. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +185 -0
  30. package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +52 -0
  31. package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +59 -0
  32. package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +47 -0
  33. package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +26 -0
  34. package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +58 -0
  35. package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +27 -0
  36. package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +34 -0
  37. package/shared-prompts/skills/sous-skills/about-agent-skills/SKILL.tpl.md +177 -0
  38. package/shared-prompts/skills/sous-skills/about-agent-skills/examples/about-something.md +45 -0
  39. package/shared-prompts/skills/sous-skills/about-agent-skills/examples/do-something.md +33 -0
  40. package/shared-prompts/skills/sous-skills/about-agent-skills/references/advanced-patterns.md +87 -0
  41. package/shared-prompts/skills/sous-skills/about-agent-skills/references/commands.md +46 -0
  42. package/shared-prompts/skills/sous-skills/about-agent-skills/references/frontmatter.md +25 -0
  43. package/shared-prompts/skills/sous-skills/about-agent-skills/references/substitutions.md +50 -0
  44. package/shared-prompts/skills/sous-skills/about-liquid-templates/SKILL.tpl.md +268 -0
  45. package/shared-prompts/skills/sous-skills/about-liquid-templates/references/liquid-filters.md +82 -0
  46. package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +51 -0
  47. package/shared-prompts/skills/sous-skills/create-skill/SKILL.tpl.md +114 -0
  48. package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +122 -0
  49. package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +80 -0
  50. package/shared-prompts/skills/task-files/go/SKILL.tpl.md +14 -0
  51. package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +13 -0
  52. package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +93 -0
  53. package/shared-prompts/skills/task-files/update/SKILL.tpl.md +14 -0
  54. package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +13 -0
  55. package/src/base-command.ts +163 -0
  56. package/src/commands/build.ts +196 -0
  57. package/src/commands/clear.ts +71 -0
  58. package/src/commands/compile.ts +95 -0
  59. package/src/commands/launch.ts +111 -0
  60. package/src/commands/prune.ts +48 -0
  61. package/src/lib/build-service.ts +258 -0
  62. package/src/lib/config-discovery.ts +199 -0
  63. package/src/lib/env-local.ts +195 -0
  64. package/src/lib/include-resolver.ts +146 -0
  65. package/src/lib/markdown-compiler.ts +580 -0
  66. package/src/lib/pid-service.ts +88 -0
  67. package/src/lib/settings.ts +695 -0
  68. package/src/lib/state.ts +135 -0
  69. package/src/lib/watch-service.ts +115 -0
  70. package/src/templating/filters/bullet-list.ts +9 -0
  71. package/src/templating/filters/index.ts +8 -0
  72. package/src/templating/init-liquid-engine.ts +82 -0
  73. package/src/templating/lib/glob-files.ts +74 -0
  74. package/src/templating/lib/import-export.ts +32 -0
  75. package/src/templating/lib/tag-args.ts +19 -0
  76. package/src/templating/tags/exportScalarVarsJs.ts +43 -0
  77. package/src/templating/tags/getFiles.ts +89 -0
  78. package/src/templating/tags/index.ts +14 -0
  79. package/src/templating/tags/listFiles.ts +54 -0
  80. package/src/templating/tags/showVars.ts +22 -0
  81. package/src/utils/formatting.ts +338 -0
  82. package/src/utils/prompts.ts +19 -0
@@ -0,0 +1,45 @@
1
+ # Example: Topical Skill (`about-*`)
2
+
3
+ ```markdown
4
+ ---
5
+ name: about-deployments
6
+ description: >
7
+ YOU MUST load this skill when working with deployments, deployment config, or when
8
+ the user mentions "deploy". Covers what a deployment is, how they work in this
9
+ project, and where deployment configuration lives.
10
+ user-invocable: false
11
+ ---
12
+
13
+ # Abstract
14
+
15
+ This "topical skill" provides information about a specific concept: deployments.
16
+
17
+ ## About Deployments
18
+
19
+ A deployment is the process of publishing a built application to a target environment.
20
+ In this project, deployments are managed via the `deploy/` directory and driven by
21
+ scripts in `deploy/scripts/`.
22
+
23
+ Deployments target three environments: `staging`, `production`, and `preview`. Each
24
+ has its own configuration file under `deploy/config/`.
25
+
26
+ ## Reference Files
27
+
28
+ - [references/environments.md](references/environments.md) — per-environment config
29
+ options, required env vars, and access requirements
30
+ - [references/rollback.md](references/rollback.md) — rollback procedures and known
31
+ failure modes
32
+
33
+ # Other Skills
34
+
35
+ ## Action Skills
36
+
37
+ Action skills for deployments will have "deploy" in their name. Find the appropriate
38
+ action skill for what you want to do. If no appropriate action skill exists, ask the
39
+ user whether one should be created before continuing.
40
+
41
+ ## Related Skills
42
+
43
+ You MUST load `about-infrastructure` for network and hosting context relevant to
44
+ deployments.
45
+ ```
@@ -0,0 +1,33 @@
1
+ # Example: Action Skill (Command)
2
+
3
+ ```markdown
4
+ ---
5
+ name: deploy
6
+ description: >
7
+ YOU MUST use this skill when deploying the application. Do not use for rollbacks —
8
+ those follow a different process.
9
+ disable-model-invocation: true
10
+ ---
11
+
12
+ # Abstract
13
+
14
+ This "action skill" (command) performs a specific operation: deploying the application
15
+ to a target environment.
16
+
17
+ A deployment publishes the built application to a hosting environment. This project
18
+ supports three environments: `staging`, `production`, and `preview`.
19
+
20
+ ## Deploying
21
+
22
+ The agent performing this work MUST load `about-deployments`. Then run the deploy
23
+ script for the target environment:
24
+
25
+ ```bash
26
+ bash ${CLAUDE_SKILL_DIR}/../../deploy/scripts/deploy.sh $ARGUMENTS
27
+ ```
28
+
29
+ # Related Skills
30
+
31
+ You MUST load `about-deployments` for environment config, required env vars, and
32
+ access requirements.
33
+ ```
@@ -0,0 +1,87 @@
1
+ # Advanced Patterns
2
+
3
+ ## Dynamic Context Injection
4
+
5
+ The `` !`command` `` syntax runs a shell command before skill content is sent to Claude.
6
+ The output replaces the placeholder — Claude receives the rendered result, not the command.
7
+
8
+ ```yaml
9
+ ---
10
+ name: pr-summary
11
+ context: fork
12
+ agent: Explore
13
+ allowed-tools: Bash(gh *)
14
+ ---
15
+
16
+ ## Pull request context
17
+ - Diff: !`gh pr diff`
18
+ - Comments: !`gh pr view --comments`
19
+ - Changed files: !`gh pr diff --name-only`
20
+
21
+ ## Task
22
+ Summarize this pull request...
23
+ ```
24
+
25
+ Execution order: each `` !`command` `` runs first, output is inserted, then Claude sees
26
+ the fully-rendered prompt. This is preprocessing, not something Claude executes.
27
+
28
+ ## Subagent Execution (`context: fork`)
29
+
30
+ Add `context: fork` to run the skill in an isolated subagent. The skill content becomes
31
+ the subagent's prompt. It does not have access to the current conversation history.
32
+
33
+ ```yaml
34
+ ---
35
+ name: deep-research
36
+ context: fork
37
+ agent: Explore
38
+ ---
39
+
40
+ Research $ARGUMENTS thoroughly:
41
+ 1. Find relevant files using Glob and Grep
42
+ 2. Read and analyze the code
43
+ 3. Summarize findings with specific file references
44
+ ```
45
+
46
+ The `agent` field selects the subagent configuration:
47
+ - `Explore` — read-only tools optimized for codebase exploration
48
+ - `Plan` — planning-oriented execution
49
+ - `general-purpose` — default; full tool access
50
+ - Any custom agent defined in `.claude/agents/`
51
+
52
+ Results are summarized and returned to the main conversation.
53
+
54
+ > Only use `context: fork` for skills with explicit task instructions. Skills containing
55
+ > only reference guidelines (e.g. "use these conventions") will return without meaningful
56
+ > output — the subagent has no actionable task.
57
+
58
+ ## Restricting Tool Access (`allowed-tools`)
59
+
60
+ Limit which tools Claude can use when a skill is active. Granted without per-use approval.
61
+
62
+ ```yaml
63
+ ---
64
+ name: safe-reader
65
+ allowed-tools: Read, Grep, Glob
66
+ ---
67
+ ```
68
+
69
+ Syntax supports wildcards: `Bash(gh *)` permits only `gh` subcommands via Bash.
70
+
71
+ ## Supporting Files
72
+
73
+ Keep `SKILL.md` under ~500 lines. Move detailed reference material to separate files
74
+ and reference them from the skill body so Claude knows when to load them.
75
+
76
+ ```
77
+ my-skill/
78
+ ├── SKILL.md # Overview, navigation, fundamental instructions
79
+ ├── references/ # Deep-dive docs — loaded when needed, not always
80
+ │ └── api-spec.md
81
+ ├── examples/ # Example outputs showing expected format
82
+ └── scripts/ # Executable scripts; referenced via $CLAUDE_SKILL_DIR
83
+ └── validate.sh
84
+ ```
85
+
86
+ Scripts are executed, not read into context. Reference them in `SKILL.md` with the
87
+ full path using `${CLAUDE_SKILL_DIR}/scripts/validate.sh`.
@@ -0,0 +1,46 @@
1
+ # Commands Reference
2
+
3
+ A **command** is a user-invocable action skill that represents an intentional,
4
+ user-initiated workflow. The user explicitly triggers it with `/skill-name`.
5
+
6
+ ## Key Properties
7
+
8
+ - Set `disable-model-invocation: true`. This removes the skill from Claude's context
9
+ entirely — it loads only when the user invokes it. See `frontmatter.md` for the
10
+ invocation matrix.
11
+ - Because the model never sees the description, write it for humans, not for Claude.
12
+ It appears in the `/` autocomplete menu. Plain, informative language is appropriate —
13
+ strong trigger language ("YOU MUST") is unnecessary and out of place.
14
+ - Commands do not require an `# Abstract` section. Include one only if useful context
15
+ is genuinely needed. Headings are optional in general — use them only if the content
16
+ is complex enough to warrant structure.
17
+
18
+ ## Arguments
19
+
20
+ Commands often accept arguments (e.g. `/deploy staging`). When they do:
21
+
22
+ 1. Use `$ARGUMENTS` (or `$0`, `$1`, etc.) in the skill body where the arguments should
23
+ be substituted. See `substitutions.md` for full syntax.
24
+ 2. Add an `argument-hint` field to frontmatter — it appears in autocomplete next to the
25
+ command name.
26
+
27
+ ```yaml
28
+ ---
29
+ name: deploy
30
+ description: Deploy the application to a target environment.
31
+ argument-hint: "[environment]"
32
+ disable-model-invocation: true
33
+ ---
34
+
35
+ Deploy to the $0 environment.
36
+ ```
37
+
38
+ If `$ARGUMENTS` is not present in the body but arguments are passed, Claude Code
39
+ appends them automatically as `ARGUMENTS: <value>`.
40
+
41
+ ## When to Omit `disable-model-invocation`
42
+
43
+ Not every command needs `disable-model-invocation: true`. If it makes sense for Claude
44
+ to invoke the command autonomously on the user's behalf (e.g. a lightweight helper with
45
+ no side effects), omit the flag. Use judgement — the defining question is whether the
46
+ action should require explicit user intent.
@@ -0,0 +1,25 @@
1
+ # Frontmatter Reference
2
+
3
+ All fields are optional, though `description` is strongly recommended.
4
+ `name` defaults to the directory name if omitted.
5
+
6
+ | Field | Description |
7
+ |:---------------------------|:------------|
8
+ | `name` | Slash-command name. Lowercase letters, numbers, hyphens (max 64 chars). Defaults to directory name. |
9
+ | `description` | When to invoke this skill. Used by Claude for auto-invocation. Falls back to first paragraph of content. |
10
+ | `argument-hint` | Hint shown in autocomplete. Example: `[issue-number]` or `[filename] [format]`. |
11
+ | `disable-model-invocation` | `true` — user-only invocation. Description removed from Claude's context entirely. Use for side-effect workflows. Default: `false`. |
12
+ | `user-invocable` | `false` — hides from `/` menu; only Claude can invoke. Use for background knowledge. Default: `true`. |
13
+ | `allowed-tools` | Tools Claude may use without per-use approval when this skill is active. Example: `Read, Grep, Glob`. |
14
+ | `model` | Model to use when this skill is active. |
15
+ | `context` | `fork` — run the skill in an isolated subagent. Skill content becomes the subagent prompt. |
16
+ | `agent` | Subagent type when `context: fork` is set. Options: `Explore`, `Plan`, `general-purpose`, or any custom agent in `.claude/agents/`. Defaults to `general-purpose`. |
17
+ | `hooks` | Hooks scoped to this skill's lifecycle. |
18
+
19
+ ## Invocation Matrix
20
+
21
+ | Frontmatter | User can invoke | Claude can invoke | When loaded into context |
22
+ |:---------------------------------|:----------------|:------------------|:--------------------------------------------------------------|
23
+ | (default) | Yes | Yes | Description always in context; full skill loads on invocation |
24
+ | `disable-model-invocation: true` | Yes | No | Not in context at all; loads only when user invokes |
25
+ | `user-invocable: false` | No | Yes | Description always in context; full skill loads on invocation |
@@ -0,0 +1,50 @@
1
+ # String Substitutions
2
+
3
+ Skills support dynamic substitution in the skill body content.
4
+
5
+ ## Argument Substitutions
6
+
7
+ | Variable | Description |
8
+ |:----------------|:------------|
9
+ | `$ARGUMENTS` | All arguments passed when invoking the skill. If not present in content, arguments are appended as `ARGUMENTS: <value>`. |
10
+ | `$ARGUMENTS[N]` | A specific argument by 0-based index. `$ARGUMENTS[0]` is the first argument. |
11
+ | `$N` | Shorthand for `$ARGUMENTS[N]`. `$0` = first argument, `$1` = second, etc. |
12
+
13
+ ### Example
14
+
15
+ ```yaml
16
+ ---
17
+ name: migrate-component
18
+ description: Migrate a component from one framework to another
19
+ ---
20
+
21
+ Migrate the $0 component from $1 to $2.
22
+ Preserve all existing behavior and tests.
23
+ ```
24
+
25
+ Running `/migrate-component SearchBar React Vue` substitutes `SearchBar`, `React`, `Vue`.
26
+
27
+ If arguments are passed but `$ARGUMENTS` (or `$N`) is not present in the content,
28
+ Claude Code appends `ARGUMENTS: <value>` to the end of the skill content automatically.
29
+
30
+ ## Session and Path Substitutions
31
+
32
+ | Variable | Description |
33
+ |:-----------------------|:------------|
34
+ | `${CLAUDE_SESSION_ID}` | The current session ID. Useful for logging or creating session-specific files. |
35
+ | `${CLAUDE_SKILL_DIR}` | Absolute path to the skill's directory. Use to reference bundled scripts or files regardless of current working directory. |
36
+
37
+ ### Example using `$CLAUDE_SKILL_DIR`
38
+
39
+ ```yaml
40
+ ---
41
+ name: run-check
42
+ allowed-tools: Bash
43
+ ---
44
+
45
+ Run the validation script:
46
+
47
+ ```bash
48
+ bash ${CLAUDE_SKILL_DIR}/scripts/validate.sh
49
+ ```
50
+ ```
@@ -0,0 +1,268 @@
1
+ ---
2
+ name: about-liquid-templates
3
+ description: >
4
+ YOU MUST load this skill when working with .tpl. files, deciding whether a file
5
+ needs .tpl. naming, writing LiquidJS syntax in templates, or using custom tags or
6
+ filters. Covers the .tpl. convention, LiquidJS syntax, custom tags, custom filters,
7
+ and a live dump of all variables available in this project.
8
+ user-invocable: false
9
+ ---
10
+
11
+ # About Liquid Templates
12
+
13
+ Files in this project with `.tpl.` in their filename are processed through LiquidJS
14
+ at compile time. The `.tpl.` segment is stripped from the output filename.
15
+
16
+ ## The `.tpl.` Convention
17
+
18
+ | Source file | Processed? | Output file |
19
+ |----------------------|------------|-------------------|
20
+ | `SKILL.md` | No | `SKILL.md` |
21
+ | `SKILL.tpl.md` | Yes | `SKILL.md` |
22
+ | `config.tpl.sh` | Yes | `config.sh` |
23
+ | `README.md` | No | `README.md` |
24
+
25
+ **`SKILL.md` for any skill compiled by sous must always be `SKILL.tpl.md`** — the
26
+ required `## Source for this Skill` footer cannot be rendered without LiquidJS
27
+ processing. See `about-agent-skills` for the full rule.
28
+
29
+ For all other files, use `.tpl.` only when the file genuinely needs variable
30
+ substitution, partials, or conditionals. Static files are copied verbatim — faster
31
+ and safer.
32
+
33
+ ## Two Syntaxes — Do Not Mix
34
+
35
+ Sous uses two different variable syntaxes at two different stages. Using the wrong one in
36
+ the wrong place silently produces the literal text instead of a value.
37
+
38
+ | Syntax | Where it belongs | Resolved by |
39
+ |--------|------------------|-------------|
40
+ | {% raw %}`${varName}`{% endraw %} | `_vars` blocks and target paths in a sous **config** file | sous, during config load |
41
+ | {% raw %}`{{ varName }}`{% endraw %} | the body of a `.tpl.` **template** file | LiquidJS, at render time |
42
+
43
+ The one crossover is `@`-include paths, which accept {% raw %}`${var}`{% endraw %}
44
+ substitution because the include processor runs before LiquidJS (see below).
45
+
46
+ ## Syntax
47
+
48
+ Output a variable:
49
+
50
+ {% raw %}
51
+ ```
52
+ {{ varName }}
53
+ ```
54
+ {% endraw %}
55
+
56
+ Conditionals:
57
+
58
+ {% raw %}
59
+ ```
60
+ {% if tool == "claude" %}
61
+ ...
62
+ {% elsif tool == "codex" %}
63
+ ...
64
+ {% else %}
65
+ ...
66
+ {% endif %}
67
+ ```
68
+ {% endraw %}
69
+
70
+ Loops:
71
+
72
+ {% raw %}
73
+ ```
74
+ {% for item in items %}{{ item }}{% endfor %}
75
+ ```
76
+ {% endraw %}
77
+
78
+ Assign a variable:
79
+
80
+ {% raw %}
81
+ ```
82
+ {% assign name = "value" %}
83
+ ```
84
+ {% endraw %}
85
+
86
+ Include another file at render time (path **relative to the template file's directory**):
87
+
88
+ {% raw %}
89
+ ```
90
+ {% render "path/to/partial.md" %}
91
+ ```
92
+ {% endraw %}
93
+
94
+ `render` resolves paths relative to the template file. For files outside that tree, use
95
+ a path **alias** (`@~sous-shared/...`, `@~project/...`, or a user-defined alias) or a
96
+ `@`-prefixed `${var}` path — the same alias resolution as `@include` (see below) works in
97
+ `render` too.
98
+
99
+ To prevent template sequences from being processed in a code example, wrap the block in
100
+ `raw` / `endraw` tag blocks. These blocks cannot be nested: the first `endraw`
101
+ encountered closes the block, so a nested pair leaks its remainder into the output. Use
102
+ one pair per code example rather than one large wrapper.
103
+
104
+ ## `@include` for Cross-Directory Files
105
+
106
+ The `@path` syntax is processed by the build system before LiquidJS runs. It includes
107
+ a file's content inline. Write `@` immediately followed by a `.md` path on its own line
108
+ with nothing else on that line.
109
+
110
+ `@include` works in both `.tpl.` and plain `.md` files. Included content is subject to
111
+ LiquidJS rendering if the parent file is a `.tpl.` (so a Liquid tag inside the included
112
+ file runs in the parent's render pass).
113
+
114
+ The engine sets both `strictVariables: false` and `strictFilters: false`. An undefined
115
+ variable renders as an empty string, and an **unknown filter silently no-ops**, passing
116
+ its input through unchanged. Neither mistake raises an error, so a typo in a variable or
117
+ filter name shows up only as missing or unfiltered output.
118
+
119
+ ### Gotcha: `@include` fires inside fenced code blocks
120
+
121
+ The `@include` processor runs on the raw file content *before* LiquidJS and has no
122
+ markdown awareness whatsoever — it matches any line that is nothing but an `@`-prefixed
123
+ `.md` path. A fenced code block does not protect it: an `@path.md` line inside triple
124
+ backticks is still executed and replaced with the file's content. There is no escape
125
+ syntax. To show an `@`-path as an example, put something else on the line (indent it,
126
+ prefix it with a word, or wrap it in backticks inline).
127
+
128
+ ### Path forms
129
+
130
+ A `@`-path may be any of:
131
+
132
+ - **Relative** to the including file: `@sections/intro.md` (traverse up with `../`).
133
+ - **Variable-substituted**: `@${sousRootPath}/shared-prompts/x.md` — `${var}` is
134
+ substituted before resolving; if the result is absolute it is used directly.
135
+ - **Aliased**: `@<alias>/rest.md`, where the first segment names a registered alias.
136
+
137
+ ### Path aliases
138
+
139
+ The first path segment, up to the first `/` or `:` (both separators work — `@a/b.md`
140
+ ≡ `@a:b.md`), is matched against the alias registry. Built-in aliases are reserved and
141
+ always begin with `~`:
142
+
143
+ - `@~sous-shared/...` → the Sous CLI's `shared-prompts` directory (skills, memories,
144
+ `_partials`, etc.). Example: `@~sous-shared/_partials/resume-task.md`.
145
+ - `@~project/...` → the consuming project's root.
146
+
147
+ Projects register their own aliases in settings via an `_aliases` block (root and/or
148
+ project level); names may **not** start with `~` (reserved). An alias value is a string
149
+ or an array of strings (each may use `${var}`):
150
+
151
+ ```
152
+ _aliases: { myDocs: "${projectRoot}/docs", shared: ["${a}", "${b}"] }
153
+ ```
154
+
155
+ ### Resolution order
156
+
157
+ For each `@`-path, candidates are tried in order and the **first that exists on disk
158
+ wins**; if none exist, the build errors listing every path tried:
159
+
160
+ 1. Each base of the matched alias, in order (project `_aliases` are tried before root,
161
+ before built-in bases of the same name).
162
+ 2. The path resolved **relative to the including file** — using the *full* path
163
+ including the alias segment. So an alias miss can fall through to a real relative
164
+ directory of the same name, letting an alias **augment** a local directory.
165
+
166
+ ## Custom Tags
167
+
168
+ **`showVars`** — dumps all variables currently in scope as a fenced JSON block.
169
+ Useful during development to see exactly what variables are available at a given point
170
+ in a template. Remove before finalizing.
171
+
172
+ **`getFiles`** — globs files under a root directory and assigns the resulting array to a
173
+ template variable. It renders nothing; present the results yourself with a `for` loop.
174
+ Each entry has `path`, `dir`, `relPath`, and `name`. `include`/`exclude` take
175
+ comma-separated glob patterns matched relative to `root`, and attribute values may be
176
+ quoted strings or scope variables. The optional `import="<exportName>"` dynamically
177
+ imports each file and attaches that export to the entry (files that fail to import, or
178
+ that lack the export, are dropped) — this is how a manifest of scripts reads its own
179
+ metadata:
180
+
181
+ {% raw %}
182
+ ```
183
+ {% getFiles tasks root=scriptsDir include="*.mjs" import="meta" %}
184
+ {% for t in tasks %}
185
+ ### {{ t.meta.name }}
186
+ - Script: `{{ t.path }}`
187
+ {% endfor %}
188
+ ```
189
+ {% endraw %}
190
+
191
+ **`listFiles`** — the convenience counterpart to `getFiles`: it globs and renders a
192
+ markdown bullet list of file names inline, with no loop needed. Add `relative="true"` to
193
+ render paths relative to the root instead of bare file names:
194
+
195
+ {% raw %}
196
+ ```
197
+ {% listFiles root=scriptsDir include="*.mjs" %}
198
+ ```
199
+ {% endraw %}
200
+
201
+ **`exportScalarVarsJs`** — emits every in-scope scalar variable (string, finite number,
202
+ boolean) as an ES module default export, keys sorted. Objects, arrays, `null` and
203
+ non-finite numbers are skipped. Use it to compile a settings module that runtime code
204
+ imports, rather than re-deriving project configuration:
205
+
206
+ {% raw %}
207
+ ```
208
+ {% exportScalarVarsJs %}
209
+ ```
210
+ {% endraw %}
211
+
212
+ ## Custom Filters
213
+
214
+ **`bulletList`** — converts an array variable to a markdown bullet list:
215
+
216
+ {% raw %}
217
+ ```
218
+ {{ tags | bulletList }}
219
+ ```
220
+ {% endraw %}
221
+
222
+ Output (if `tags` is `["a", "b", "c"]`):
223
+ ```
224
+ - a
225
+ - b
226
+ - c
227
+ ```
228
+
229
+ Given a non-array value, `bulletList` returns it as a plain string with no bullet.
230
+
231
+ ## Authoring Guidelines
232
+
233
+ Templates (`.tpl.*` files) must be **maximally reusable**. A well-written template
234
+ can be copied between projects or shared across teams without edits — only the
235
+ project's variables change.
236
+
237
+ **Rules:**
238
+
239
+ 1. **Never hard-code values that could differ between projects.** If a value comes
240
+ from project configuration (board IDs, project keys, sprint IDs, URLs, user info),
241
+ use a variable. When no suitable variable exists, add one to the project's sous
242
+ config file (the file that defines the project's `_vars`).
243
+ 2. **Guard project-specific blocks with conditionals.** If a section only applies when
244
+ a variable is set, wrap it in {% raw %}`{% if varName %} ... {% endif %}`{% endraw %} so the
245
+ block disappears cleanly for projects that don't define it.
246
+ 3. **Prefer derived variables over raw values.** Example: `ticketPrefix` is derived
247
+ from `jiraProjectKey` — templates use `ticketPrefix` so they stay correct if the
248
+ key changes.
249
+ 4. **Test portability mentally.** Before finalizing a template, ask: "If I compiled
250
+ this for a different project with different settings, would the output still make
251
+ sense?" If not, parameterize the varying part.
252
+
253
+ ## Reference Files
254
+
255
+ - [liquid-filters.md](references/liquid-filters.md) — complete standard LiquidJS filter catalogue (string, array, number, date, default)
256
+
257
+ ## Available Variables
258
+
259
+ The following variables are in scope at compile time in this project:
260
+
261
+ {% showVars %}
262
+
263
+ ## Source for this Skill
264
+
265
+ This skill was pulled from the `sous` project's "shared skills" library. It was compiled from a template and
266
+ the output file should not be edited directly.
267
+
268
+ - Source Path: {{ sousTemplatePath }}
@@ -0,0 +1,82 @@
1
+ # LiquidJS Built-in Filters
2
+
3
+ Standard filters available in all `.tpl.` files. Chained with `|`.
4
+
5
+ ## String
6
+
7
+ | Filter | Description | Example |
8
+ |:-------|:------------|:--------|
9
+ | `upcase` | Uppercase | `{{ "hello" | upcase }}` → `HELLO` |
10
+ | `downcase` | Lowercase | `{{ "HELLO" | downcase }}` → `hello` |
11
+ | `capitalize` | First char uppercase | `{{ "hello world" | capitalize }}` → `Hello world` |
12
+ | `strip` | Remove leading/trailing whitespace | `{{ " hi " | strip }}` → `hi` |
13
+ | `lstrip` | Remove leading whitespace | |
14
+ | `rstrip` | Remove trailing whitespace | |
15
+ | `strip_newlines` | Remove all newlines | |
16
+ | `strip_html` | Remove HTML tags | |
17
+ | `escape` | HTML-escape `<`, `>`, `&`, `"` | |
18
+ | `url_encode` | Percent-encode a URL string | |
19
+ | `url_decode` | Decode a percent-encoded string | |
20
+ | `append` | Append a string | `{{ "foo" | append: "bar" }}` → `foobar` |
21
+ | `prepend` | Prepend a string | `{{ "bar" | prepend: "foo" }}` → `foobar` |
22
+ | `replace` | Replace all occurrences | `{{ "aabbcc" | replace: "b", "x" }}` → `aaxxcc` |
23
+ | `replace_first` | Replace first occurrence | |
24
+ | `remove` | Remove all occurrences | `{{ "aabbcc" | remove: "b" }}` → `aacc` |
25
+ | `remove_first` | Remove first occurrence | |
26
+ | `truncate` | Truncate to N chars (adds `...`) | `{{ "hello world" | truncate: 7 }}` → `hell...` |
27
+ | `truncatewords` | Truncate to N words | `{{ "one two three" | truncatewords: 2 }}` → `one two...` |
28
+ | `split` | Split string into array | `{{ "a,b,c" | split: "," }}` → `["a","b","c"]` |
29
+ | `newline_to_br` | Replace `\n` with `<br>` | |
30
+
31
+ ## Array
32
+
33
+ | Filter | Description | Example |
34
+ |:-------|:------------|:--------|
35
+ | `join` | Join array with separator | `{{ arr | join: ", " }}` |
36
+ | `first` | First element | `{{ arr | first }}` |
37
+ | `last` | Last element | `{{ arr | last }}` |
38
+ | `reverse` | Reverse order | |
39
+ | `sort` | Sort ascending (case-sensitive) | |
40
+ | `sort_natural` | Sort ascending (case-insensitive) | |
41
+ | `uniq` | Remove duplicates | |
42
+ | `compact` | Remove nil/falsy values | |
43
+ | `map` | Extract a property from each object | `{{ items | map: "name" }}` |
44
+ | `where` | Filter objects by property value | `{{ items | where: "active", true }}` |
45
+ | `concat` | Concatenate two arrays | `{{ arr1 | concat: arr2 }}` |
46
+ | `slice` | Extract a sub-array | `{{ arr | slice: 1, 3 }}` |
47
+ | `size` | Length of string or array | `{{ arr | size }}` |
48
+ | `push` | Append element to array | |
49
+ | `pop` | Remove last element | |
50
+ | `shift` | Remove first element | |
51
+ | `unshift` | Prepend element to array | |
52
+
53
+ ## Number
54
+
55
+ | Filter | Description | Example |
56
+ |:-------|:------------|:--------|
57
+ | `plus` | Add | `{{ 4 | plus: 2 }}` → `6` |
58
+ | `minus` | Subtract | `{{ 4 | minus: 2 }}` → `2` |
59
+ | `times` | Multiply | `{{ 4 | times: 2 }}` → `8` |
60
+ | `divided_by` | Divide | `{{ 10 | divided_by: 2 }}` → `5` |
61
+ | `modulo` | Modulus | `{{ 10 | modulo: 3 }}` → `1` |
62
+ | `abs` | Absolute value | `{{ -4 | abs }}` → `4` |
63
+ | `ceil` | Round up | `{{ 4.1 | ceil }}` → `5` |
64
+ | `floor` | Round down | `{{ 4.9 | floor }}` → `4` |
65
+ | `round` | Round to nearest (or N decimal places) | `{{ 4.567 | round: 2 }}` → `4.57` |
66
+ | `at_least` | Clamp to minimum | `{{ 3 | at_least: 5 }}` → `5` |
67
+ | `at_most` | Clamp to maximum | `{{ 7 | at_most: 5 }}` → `5` |
68
+
69
+ ## Other
70
+
71
+ | Filter | Description | Example |
72
+ |:-------|:------------|:--------|
73
+ | `default` | Fallback if nil/empty/false | `{{ val | default: "n/a" }}` |
74
+ | `date` | Format a date | `{{ "now" | date: "%Y-%m-%d" }}` |
75
+ | `size` | Length of string or array | `{{ "hello" | size }}` → `5` |
76
+ | `json` | Serialize to JSON string | `{{ obj | json }}` |
77
+
78
+ ## Sous Custom Filters
79
+
80
+ | Filter | Description |
81
+ |:-------|:------------|
82
+ | `bulletList` | Convert array to markdown bullet list (`- item` per line) |