@softspark/ai-toolkit 1.3.3 → 1.3.5
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/CHANGELOG.md +14 -0
- package/app/hooks/governance-capture.sh +11 -2
- package/app/hooks/post-tool-use.sh +4 -2
- package/app/hooks/pre-compact-save.sh +4 -1
- package/app/hooks/save-session.sh +9 -4
- package/app/hooks/session-context.sh +4 -1
- package/app/hooks/subagent-start.sh +4 -2
- package/app/hooks/subagent-stop.sh +4 -2
- package/app/hooks/track-usage.sh +5 -1
- package/app/hooks/user-prompt-submit.sh +3 -1
- package/bin/ai-toolkit.js +5 -0
- package/kb/reference/skills-catalog.md +8 -2
- package/llms-full.txt +8 -8
- package/package.json +1 -1
- package/scripts/add_rule.py +5 -0
- package/scripts/plugin.py +3 -0
- package/scripts/sync.py +27 -10
- package/scripts/validate.py +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v1.3.5 — Patch (2026-04-07)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **Stats not counting**: `track-usage.sh` and `user-prompt-submit.sh` now read prompt from stdin JSON (`.prompt` field) instead of non-existent `CLAUDE_USER_PROMPT` env var. Skill invocations are now properly tracked in `~/.ai-toolkit/stats.json`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## v1.3.4 — Patch (2026-04-07)
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- **skills-catalog.md**: Added 6 missing language pattern skills (rust, java, csharp, kotlin, swift, ruby) to Development section (10→16). Total now correctly sums to 90.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
10
24
|
## v1.3.3 — Patch (2026-04-07)
|
|
11
25
|
|
|
12
26
|
### Fixed
|
|
@@ -24,8 +24,17 @@ log_event() {
|
|
|
24
24
|
local EVENT="$1"
|
|
25
25
|
local DETAIL="$2"
|
|
26
26
|
mkdir -p "$LOG_DIR"
|
|
27
|
-
|
|
28
|
-
"$TIMESTAMP" "$EVENT" "$TOOL_NAME" "$DETAIL" "$SESSION"
|
|
27
|
+
if command -v jq >/dev/null 2>&1; then
|
|
28
|
+
jq -nc --arg ts "$TIMESTAMP" --arg ev "$EVENT" --arg tl "$TOOL_NAME" --arg dt "$DETAIL" --arg ss "$SESSION" \
|
|
29
|
+
'{"timestamp":$ts,"event":$ev,"tool":$tl,"detail":$dt,"session":$ss}' >> "$LOG_FILE"
|
|
30
|
+
else
|
|
31
|
+
# Escape double quotes in variable fields for basic JSON safety
|
|
32
|
+
local SAFE_DETAIL="${DETAIL//\"/\\\"}"
|
|
33
|
+
local SAFE_TOOL="${TOOL_NAME//\"/\\\"}"
|
|
34
|
+
local SAFE_SESSION="${SESSION//\"/\\\"}"
|
|
35
|
+
printf '{"timestamp":"%s","event":"%s","tool":"%s","detail":"%s","session":"%s"}\n' \
|
|
36
|
+
"$TIMESTAMP" "$EVENT" "$SAFE_TOOL" "$SAFE_DETAIL" "$SAFE_SESSION" >> "$LOG_FILE"
|
|
37
|
+
fi
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
case "$TOOL_NAME" in
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
# shellcheck source=_profile-check.sh
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
# Read from stdin (Claude Code passes JSON with .tool_name, .tool_input)
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // "unknown"' 2>/dev/null)
|
|
14
|
+
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
13
15
|
|
|
14
16
|
if [ -z "$FILE_PATH" ]; then
|
|
15
17
|
echo "PostToolUse: ${TOOL_NAME} completed. Consider validating lint, tests, and docs if behavior changed."
|
|
@@ -11,8 +11,11 @@ source "$(dirname "$0")/_profile-check.sh"
|
|
|
11
11
|
SAVE_DIR="$HOME/.ai-toolkit/compactions"
|
|
12
12
|
mkdir -p "$SAVE_DIR"
|
|
13
13
|
|
|
14
|
+
# Read from stdin (Claude Code passes JSON with .session_id)
|
|
15
|
+
INPUT=$(cat)
|
|
14
16
|
TIMESTAMP=$(date -u +"%Y-%m-%d_%H-%M-%S")
|
|
15
|
-
SESSION
|
|
17
|
+
SESSION=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
|
18
|
+
[ -z "$SESSION" ] && SESSION="$$"
|
|
16
19
|
SAVE_FILE="$SAVE_DIR/${TIMESTAMP}_${SESSION}.txt"
|
|
17
20
|
|
|
18
21
|
# Gather context
|
|
@@ -7,17 +7,22 @@
|
|
|
7
7
|
# shellcheck source=_profile-check.sh
|
|
8
8
|
source "$(dirname "$0")/_profile-check.sh"
|
|
9
9
|
|
|
10
|
+
# Read from stdin (Claude Code passes JSON with .session_id, .last_assistant_message)
|
|
11
|
+
INPUT=$(cat)
|
|
12
|
+
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
|
13
|
+
LAST_MSG=$(echo "$INPUT" | jq -r '.last_assistant_message // "No summary available"' 2>/dev/null | head -5)
|
|
14
|
+
|
|
10
15
|
SESSION_FILE=".claude/session-context.md"
|
|
11
16
|
|
|
12
|
-
if [ -n "$
|
|
17
|
+
if [ -n "$SESSION_ID" ]; then
|
|
13
18
|
mkdir -p .claude
|
|
14
19
|
cat > "$SESSION_FILE" << EOF
|
|
15
20
|
# Session Context
|
|
16
21
|
Updated: $(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
17
|
-
Session: ${
|
|
22
|
+
Session: ${SESSION_ID}
|
|
18
23
|
|
|
19
|
-
## Last
|
|
20
|
-
${
|
|
24
|
+
## Last Assistant Message
|
|
25
|
+
${LAST_MSG}
|
|
21
26
|
EOF
|
|
22
27
|
fi
|
|
23
28
|
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
# shellcheck source=_profile-check.sh
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Read from stdin (Claude Code passes JSON with .session_id)
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
SESSION=$(echo "$INPUT" | jq -r '.session_id // empty' 2>/dev/null)
|
|
14
|
+
[ -z "$SESSION" ] && SESSION="$$"
|
|
12
15
|
SESSIONS_DIR="$HOME/.ai-toolkit/sessions"
|
|
13
16
|
mkdir -p "$SESSIONS_DIR"
|
|
14
17
|
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
# shellcheck source=_profile-check.sh
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Read from stdin (Claude Code passes JSON with .agent_id, .agent_type)
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type // "subagent"' 2>/dev/null)
|
|
12
14
|
|
|
13
|
-
echo "SubagentStart: ${
|
|
15
|
+
echo "SubagentStart: ${AGENT_TYPE} owns a narrow scope. Read only the necessary files first, cite evidence, and return explicit validation notes with any edits."
|
|
14
16
|
|
|
15
17
|
exit 0
|
|
16
18
|
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
# shellcheck source=_profile-check.sh
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Read from stdin (Claude Code passes JSON with .agent_id, .agent_type)
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
AGENT_TYPE=$(echo "$INPUT" | jq -r '.agent_type // "subagent"' 2>/dev/null)
|
|
12
14
|
|
|
13
|
-
echo "SubagentStop: ${
|
|
15
|
+
echo "SubagentStop: ${AGENT_TYPE} should report findings, exact files touched, tests run, remaining risks, and any docs that must be updated by the lead agent."
|
|
14
16
|
|
|
15
17
|
exit 0
|
|
16
18
|
|
package/app/hooks/track-usage.sh
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
# Uses atomic write via python3 os.replace() to prevent corruption.
|
|
9
9
|
|
|
10
10
|
STATS_FILE="${HOME}/.ai-toolkit/stats.json"
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
# Read prompt from stdin (Claude Code passes JSON with .prompt field)
|
|
13
|
+
INPUT=$(cat)
|
|
14
|
+
PROMPT_TEXT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
|
|
15
|
+
[ -z "$PROMPT_TEXT" ] && exit 0
|
|
12
16
|
|
|
13
17
|
# Only track if prompt starts with a slash command
|
|
14
18
|
SKILL_NAME=$(printf '%s' "$PROMPT_TEXT" | grep -oE '^/[a-z][a-z0-9-]*' | head -1 | sed 's|^/||')
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
# shellcheck source=_profile-check.sh
|
|
9
9
|
source "$(dirname "$0")/_profile-check.sh"
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Read prompt from stdin (Claude Code passes JSON with .prompt field)
|
|
12
|
+
INPUT=$(cat)
|
|
13
|
+
PROMPT_TEXT=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
|
|
12
14
|
LOWERED="$(printf '%s' "$PROMPT_TEXT" | tr '[:upper:]' '[:lower:]')"
|
|
13
15
|
|
|
14
16
|
echo "STOP. Execute Step 0 before responding: check your CLAUDE.md for search-first rules. If search-first rules exist, call the required search tool NOW — before any other tool or text output. If you skip this step, the user will interrupt you."
|
package/bin/ai-toolkit.js
CHANGED
|
@@ -8,6 +8,11 @@ const fs = require('fs');
|
|
|
8
8
|
const TOOLKIT_DIR = path.dirname(__dirname);
|
|
9
9
|
const CWD = process.cwd();
|
|
10
10
|
|
|
11
|
+
if (!process.env.HOME) {
|
|
12
|
+
console.error('Error: HOME environment variable is not set');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
// ---------------------------------------------------------------------------
|
|
12
17
|
// Generator map: command name -> { script, dest, mkdir? }
|
|
13
18
|
// Used by individual generator commands, `generate-all`, and the default case.
|
|
@@ -3,7 +3,7 @@ title: "AI Toolkit - Skills Catalog"
|
|
|
3
3
|
category: reference
|
|
4
4
|
service: ai-toolkit
|
|
5
5
|
tags: [skills, domain-knowledge, catalog, task-skills, hybrid-skills]
|
|
6
|
-
version: "1.3.
|
|
6
|
+
version: "1.3.3"
|
|
7
7
|
created: "2026-03-23"
|
|
8
8
|
last_updated: "2026-04-07"
|
|
9
9
|
description: "Complete catalog of 90 skills: 28 task, 30 hybrid, 32 knowledge. Includes effort levels, skill-scoped hooks, executable scripts, security auditor, and persona presets."
|
|
@@ -113,7 +113,7 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
|
|
|
113
113
|
| `application-deploy` | 3 | Deploy → smoke test → release notes |
|
|
114
114
|
| `proactive-troubleshooting` | 4 | Investigate → check perf → preventive fix → docs |
|
|
115
115
|
|
|
116
|
-
## Knowledge Skills - Development (
|
|
116
|
+
## Knowledge Skills - Development (16)
|
|
117
117
|
|
|
118
118
|
| Skill | Directory | Domain |
|
|
119
119
|
|-------|-----------|--------|
|
|
@@ -124,6 +124,12 @@ Hybrid skills combine slash-command invocation with domain knowledge that agents
|
|
|
124
124
|
| **ecommerce-patterns** | `skills/ecommerce-patterns/` | E-commerce: catalog, cart, checkout, payments |
|
|
125
125
|
| **clean-code** | `skills/clean-code/` | Multi-language code quality: Python, TS, PHP, Go, Dart |
|
|
126
126
|
| **typescript-patterns** | `skills/typescript-patterns/` | TypeScript/JavaScript patterns for frontend and backend |
|
|
127
|
+
| **rust-patterns** | `skills/rust-patterns/` | Ownership, borrowing, error handling, Cargo, tokio, serde |
|
|
128
|
+
| **java-patterns** | `skills/java-patterns/` | Records, sealed classes, Stream API, Spring Boot, JUnit 5 |
|
|
129
|
+
| **csharp-patterns** | `skills/csharp-patterns/` | Nullable refs, async/await, ASP.NET Core, EF Core |
|
|
130
|
+
| **kotlin-patterns** | `skills/kotlin-patterns/` | Coroutines, DSLs, sealed classes, Ktor, MockK |
|
|
131
|
+
| **swift-patterns** | `skills/swift-patterns/` | Protocol-oriented, SwiftUI, async/await, SPM |
|
|
132
|
+
| **ruby-patterns** | `skills/ruby-patterns/` | Blocks, Rails conventions, RSpec, ActiveRecord |
|
|
127
133
|
| **design-engineering** | `skills/design-engineering/` | UI polish, animation craft, easing, transforms, accessibility |
|
|
128
134
|
| **documentation-standards** | `skills/documentation-standards/` | KB document conventions, frontmatter validation, category taxonomy |
|
|
129
135
|
| **brand-voice** | `skills/brand-voice/` | Anti-trope list, voice principles, LLM rhetoric prevention |
|
package/llms-full.txt
CHANGED
|
@@ -3343,10 +3343,10 @@ The `common/` directory uses the same structure except `frameworks.md` is replac
|
|
|
3343
3343
|
|
|
3344
3344
|
## Auto-Detection
|
|
3345
3345
|
|
|
3346
|
-
|
|
3346
|
+
`--local` automatically enables language auto-detection. `scripts/install_steps/detect_language.py` scans the current directory for known marker files and selects matching language modules:
|
|
3347
3347
|
|
|
3348
3348
|
```bash
|
|
3349
|
-
ai-toolkit install --local --auto-detect
|
|
3349
|
+
ai-toolkit install --local # auto-detects language (--auto-detect is implied)
|
|
3350
3350
|
```
|
|
3351
3351
|
|
|
3352
3352
|
Detection logic (first match wins when multiple markers are present):
|
|
@@ -3368,14 +3368,14 @@ Common rules are always injected regardless of detected language.
|
|
|
3368
3368
|
## Installation
|
|
3369
3369
|
|
|
3370
3370
|
```bash
|
|
3371
|
-
# Auto-detect language from project files
|
|
3372
|
-
ai-toolkit install --local
|
|
3371
|
+
# Auto-detect language from project files (default with --local)
|
|
3372
|
+
ai-toolkit install --local
|
|
3373
3373
|
|
|
3374
3374
|
# Explicitly select a language
|
|
3375
3375
|
ai-toolkit install --local --lang typescript
|
|
3376
3376
|
|
|
3377
|
-
#
|
|
3378
|
-
ai-toolkit install --local
|
|
3377
|
+
# Skip auto-detect, install specific modules only
|
|
3378
|
+
ai-toolkit install --local --modules core,agents
|
|
3379
3379
|
```
|
|
3380
3380
|
|
|
3381
3381
|
Language rules are injected into the project `CLAUDE.md` between named markers:
|
|
@@ -3491,8 +3491,8 @@ ai-toolkit install --profile standard
|
|
|
3491
3491
|
# Module-based install (new)
|
|
3492
3492
|
ai-toolkit install --modules core,agents,rules-typescript
|
|
3493
3493
|
|
|
3494
|
-
#
|
|
3495
|
-
ai-toolkit install --local
|
|
3494
|
+
# --local implies --auto-detect (language rules auto-detected)
|
|
3495
|
+
ai-toolkit install --local
|
|
3496
3496
|
|
|
3497
3497
|
# Show currently installed modules and their state
|
|
3498
3498
|
ai-toolkit status
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softspark/ai-toolkit",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Professional-grade AI coding toolkit: 90 skills, 44 agents, multi-platform support (Claude, Cursor, Windsurf, Copilot, Gemini, Cline, Roo Code, Aider, Augment), machine-enforced safety constitution, persona presets, skill security auditor, expanded lifecycle hooks, 11 plugin packs, and benchmark tooling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
package/scripts/add_rule.py
CHANGED
|
@@ -14,6 +14,7 @@ Arguments:
|
|
|
14
14
|
"""
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
import re
|
|
17
18
|
import shutil
|
|
18
19
|
import sys
|
|
19
20
|
from pathlib import Path
|
|
@@ -33,6 +34,10 @@ def main() -> None:
|
|
|
33
34
|
sys.exit(1)
|
|
34
35
|
|
|
35
36
|
rule_name = sys.argv[2] if len(sys.argv) > 2 else rule_file.stem
|
|
37
|
+
rule_name = re.sub(r"[^a-zA-Z0-9_-]", "", rule_name)
|
|
38
|
+
if not rule_name:
|
|
39
|
+
print("Error: rule name is empty after sanitization", file=sys.stderr)
|
|
40
|
+
sys.exit(1)
|
|
36
41
|
rules_dir = Path.home() / ".ai-toolkit" / "rules"
|
|
37
42
|
rules_dir.mkdir(parents=True, exist_ok=True)
|
|
38
43
|
|
package/scripts/plugin.py
CHANGED
|
@@ -588,6 +588,9 @@ def _parse_clean_args(args: list[str]) -> tuple[list[str], int]:
|
|
|
588
588
|
if args[i] == "--days" and i + 1 < len(args):
|
|
589
589
|
try:
|
|
590
590
|
days = int(args[i + 1])
|
|
591
|
+
if days <= 0:
|
|
592
|
+
print(f" ERROR: --days must be positive, got {days}")
|
|
593
|
+
sys.exit(1)
|
|
591
594
|
except ValueError:
|
|
592
595
|
print(f" ERROR: --days requires a number, got '{args[i + 1]}'")
|
|
593
596
|
sys.exit(1)
|
package/scripts/sync.py
CHANGED
|
@@ -66,9 +66,23 @@ def do_import(source: str) -> None:
|
|
|
66
66
|
"""Import config from file or URL."""
|
|
67
67
|
tmpfile: str | None = None
|
|
68
68
|
|
|
69
|
-
if source.startswith("http"):
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
if source.startswith("https://") or source.startswith("http://"):
|
|
70
|
+
if not source.startswith("https://"):
|
|
71
|
+
print("Error: HTTP imports are not supported. Use HTTPS for security.", file=sys.stderr)
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
tmp_fd, tmpfile = tempfile.mkstemp(suffix=".json")
|
|
74
|
+
os.close(tmp_fd)
|
|
75
|
+
try:
|
|
76
|
+
import ssl
|
|
77
|
+
ctx = ssl.create_default_context()
|
|
78
|
+
with urllib.request.urlopen(source, timeout=30, context=ctx) as resp:
|
|
79
|
+
data = resp.read(10 * 1024 * 1024) # 10MB max
|
|
80
|
+
with open(tmpfile, 'wb') as f:
|
|
81
|
+
f.write(data)
|
|
82
|
+
except Exception:
|
|
83
|
+
if os.path.exists(tmpfile):
|
|
84
|
+
os.unlink(tmpfile)
|
|
85
|
+
raise
|
|
72
86
|
source = tmpfile
|
|
73
87
|
|
|
74
88
|
source_path = Path(source)
|
|
@@ -112,7 +126,8 @@ def do_push() -> None:
|
|
|
112
126
|
print("Error: gh not authenticated. Run: gh auth login")
|
|
113
127
|
sys.exit(1)
|
|
114
128
|
|
|
115
|
-
tmpfile = tempfile.
|
|
129
|
+
tmp_fd, tmpfile = tempfile.mkstemp(suffix=".json")
|
|
130
|
+
os.close(tmp_fd)
|
|
116
131
|
Path(tmpfile).write_text(do_export(), encoding="utf-8")
|
|
117
132
|
|
|
118
133
|
if GIST_ID_FILE.is_file():
|
|
@@ -153,12 +168,14 @@ def do_pull(gist_id: str = "") -> None:
|
|
|
153
168
|
print("Usage: ai-toolkit sync --pull <gist-id>")
|
|
154
169
|
sys.exit(1)
|
|
155
170
|
|
|
156
|
-
tmpfile = tempfile.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
tmp_fd, tmpfile = tempfile.mkstemp(suffix=".json")
|
|
172
|
+
os.close(tmp_fd)
|
|
173
|
+
with open(tmpfile, "w") as f:
|
|
174
|
+
subprocess.run(
|
|
175
|
+
["gh", "gist", "view", gist_id, "-f", "ai-toolkit-config.json"],
|
|
176
|
+
stdout=f,
|
|
177
|
+
check=True,
|
|
178
|
+
)
|
|
162
179
|
do_import(tmpfile)
|
|
163
180
|
os.unlink(tmpfile)
|
|
164
181
|
|
package/scripts/validate.py
CHANGED
|
@@ -30,7 +30,7 @@ from plugin_schema import validate_references as _validate_plugin_references
|
|
|
30
30
|
# ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
VALID_TOOLS = frozenset({
|
|
33
|
-
"Read", "Write", "Edit", "Bash", "Grep", "Glob", "Agent",
|
|
33
|
+
"Read", "Write", "Edit", "MultiEdit", "Bash", "Grep", "Glob", "Agent",
|
|
34
34
|
"WebSearch", "WebFetch", "TodoRead", "TodoWrite",
|
|
35
35
|
"TeamCreate", "TeamDelete", "SendMessage",
|
|
36
36
|
"TaskCreate", "TaskList", "TaskUpdate", "TaskGet", "TaskOutput", "TaskStop",
|