@erclx/canon 5.8.0 → 5.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/git-followup/SKILL.md +1 -1
- package/claude/skills/git-pr/references/evidence.md +1 -1
- package/claude/skills/review-craft/REQUIREMENT.md +8 -2
- package/claude/skills/review-craft/SKILL.md +63 -7
- package/claude/skills/review-craft/references/adopted.md +49 -0
- package/docs/agents/commands.md +2 -0
- package/docs/agents/hooks.md +56 -0
- package/docs/agents/index.md +1 -0
- package/docs/agents/install-and-sync.md +1 -1
- package/docs/agents/markdown-audit.md +1 -1
- package/docs/agents/pr-evidence.md +33 -17
- package/docs/agents/records-push.md +1 -1
- package/docs/agents/records-validate.md +1 -1
- package/docs/agents/scripting.md +6 -0
- package/docs/agents/tasks-archive.md +4 -2
- package/docs/agents/tasks.md +1 -1
- package/docs/agents/worktrees.md +2 -2
- package/docs/target/scenarios.md +2 -2
- package/package.json +1 -1
- package/src/claude/skills-audit.ts +1 -0
- package/src/cli.ts +2 -0
- package/src/commands/claude.ts +1 -1
- package/src/commands/docs.ts +51 -11
- package/src/commands/hooks.ts +67 -0
- package/src/commands/pr.ts +146 -76
- package/src/commands/standards.ts +33 -2
- package/src/commands/tasks.ts +45 -0
- package/src/commands/tooling.ts +3 -3
- package/src/commands/upgrade.ts +3 -2
- package/src/commands/worktrees.ts +4 -3
- package/src/docs/list.ts +103 -0
- package/src/docs/read.ts +63 -19
- package/src/gov/stacks.ts +1 -1
- package/src/help.ts +2 -0
- package/src/hooks/post-merge.ts +331 -0
- package/src/pr/evidence.ts +21 -0
- package/src/records/validate.ts +1 -38
- package/src/standards/list.ts +33 -0
- package/src/standards/read.ts +13 -0
- package/src/standards/scope.ts +56 -0
- package/src/sync/check.ts +38 -4
- package/src/tasks/archive.ts +168 -3
- package/src/tooling/inject.ts +4 -3
- package/src/tooling/manifest.ts +62 -8
- package/src/tooling/scan.ts +4 -3
- package/src/tooling/stamp.ts +11 -2
- package/src/tooling/subfolder.ts +7 -3
- package/tooling/astro/reference.md +1 -1
- package/tooling/base/configs/scripts/verify.json +3 -0
- package/tooling/base/configs/scripts/verify.sh +1 -110
- package/tooling/base/configs/scripts/verify.ts +378 -0
- package/tooling/base/manifest.toml +1 -0
- package/tooling/base/reference.md +5 -3
- package/tooling/go/configs/scripts/verify.json +10 -0
- package/tooling/go/reference.md +5 -4
- package/tooling/php/configs/scripts/verify.json +7 -0
- package/tooling/php/reference.md +5 -4
- package/tooling/python/configs/scripts/verify.json +4 -0
- package/tooling/python/reference.md +3 -3
- package/tooling/web/configs/scripts/verify.json +3 -0
- package/tooling/web/reference.md +3 -3
- package/scripts/docs/list.sh +0 -184
- package/scripts/lib/frontmatter.sh +0 -30
- package/scripts/standards/list.sh +0 -115
- package/src/commands/pass-through.ts +0 -39
- package/tooling/go/configs/scripts/verify.sh +0 -142
- package/tooling/php/configs/scripts/verify.sh +0 -147
- package/tooling/python/configs/scripts/verify.sh +0 -141
- package/tooling/web/configs/scripts/verify.sh +0 -153
package/scripts/docs/list.sh
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(dirname "$(dirname "$SCRIPT_DIR")")}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
source "$PROJECT_ROOT/scripts/lib/frontmatter.sh"
|
|
10
|
-
|
|
11
|
-
DOCS_DIR="$PROJECT_ROOT/docs"
|
|
12
|
-
CONTEXT_DIR="$PROJECT_ROOT/canon/context"
|
|
13
|
-
|
|
14
|
-
INTERNAL_TOPICS=" ci development sandbox "
|
|
15
|
-
|
|
16
|
-
show_help() {
|
|
17
|
-
echo -e "${GREY}┌${NC}"
|
|
18
|
-
echo -e "${GREY}├${NC} ${WHITE}Usage:${NC} canon docs list [options]"
|
|
19
|
-
echo -e "${GREY}│${NC}"
|
|
20
|
-
echo -e "${GREY}│${NC} ${WHITE}Options:${NC}"
|
|
21
|
-
echo -e "${GREY}│${NC} --json ${GREY}# Emit machine-readable JSON${NC}"
|
|
22
|
-
echo -e "${GREY}│${NC} -h, --help ${GREY}# Show this help message${NC}"
|
|
23
|
-
echo -e "${GREY}└${NC}"
|
|
24
|
-
exit 0
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
is_target_facing() {
|
|
28
|
-
case "$1" in
|
|
29
|
-
"Agent surface" | "Domain references") return 0 ;;
|
|
30
|
-
*) return 1 ;;
|
|
31
|
-
esac
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
is_internal_topic() {
|
|
35
|
-
case "$INTERNAL_TOPICS" in
|
|
36
|
-
*" $1 "*) return 0 ;;
|
|
37
|
-
*) return 1 ;;
|
|
38
|
-
esac
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Emits `name<TAB>description<TAB>category<TAB>target` per target-facing doc,
|
|
42
|
-
# sorted so a domain split into a folder lands in its alphabetical place rather
|
|
43
|
-
# than after every file. A folder declares its category on its own index, since
|
|
44
|
-
# the allowlist is what separates a target-facing doc from a workflow one and a
|
|
45
|
-
# split domain is not exempt from it. A sub-area file declares its own, which is
|
|
46
|
-
# what keeps a page's listing membership unchanged by the folder it moves into.
|
|
47
|
-
collect_docs() {
|
|
48
|
-
local file name description category
|
|
49
|
-
{
|
|
50
|
-
while IFS= read -r file; do
|
|
51
|
-
name=$(basename "$file" .md)
|
|
52
|
-
[ "$name" = "index" ] && continue
|
|
53
|
-
category=$(read_frontmatter_field "$file" "category")
|
|
54
|
-
is_target_facing "$category" || continue
|
|
55
|
-
description=$(read_frontmatter_field "$file" "description")
|
|
56
|
-
printf '%s\t%s\t%s\t%s\n' "$name" "$description" "$category" "docs/$name.md"
|
|
57
|
-
done < <(find "$DOCS_DIR" -maxdepth 1 -type f -name "*.md")
|
|
58
|
-
|
|
59
|
-
# A split domain is named by its folder and described by its generated
|
|
60
|
-
# index, which carries subtitle where a sibling file carries description
|
|
61
|
-
while IFS= read -r file; do
|
|
62
|
-
name=$(basename "$(dirname "$file")")
|
|
63
|
-
category=$(read_frontmatter_field "$file" "category")
|
|
64
|
-
is_target_facing "$category" || continue
|
|
65
|
-
description=$(read_frontmatter_field "$file" "subtitle")
|
|
66
|
-
printf '%s\t%s\t%s\t%s\n' "$name" "$description" "$category" "docs/$name/index.md"
|
|
67
|
-
done < <(find "$DOCS_DIR" -mindepth 2 -maxdepth 2 -type f -name "index.md")
|
|
68
|
-
|
|
69
|
-
# A sub-area file is named and described like a sibling file, and reaches a
|
|
70
|
-
# caller by that bare name, so the depth it sits at is not the listing's
|
|
71
|
-
# business. Only a folder carrying an index is walked, matching resolveTopic
|
|
72
|
-
while IFS= read -r file; do
|
|
73
|
-
name=$(basename "$file" .md)
|
|
74
|
-
[ "$name" = "index" ] && continue
|
|
75
|
-
[ -f "$(dirname "$file")/index.md" ] || continue
|
|
76
|
-
category=$(read_frontmatter_field "$file" "category")
|
|
77
|
-
is_target_facing "$category" || continue
|
|
78
|
-
description=$(read_frontmatter_field "$file" "description")
|
|
79
|
-
printf '%s\t%s\t%s\t%s\n' "$name" "$description" "$category" "docs/$(basename "$(dirname "$file")")/$name.md"
|
|
80
|
-
done < <(find "$DOCS_DIR" -mindepth 2 -maxdepth 2 -type f -name "*.md")
|
|
81
|
-
} | sort
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
# Emits `name<TAB>description<TAB>target` per context entry, sorted so a domain
|
|
85
|
-
# split into a folder lands in its alphabetical place rather than after every
|
|
86
|
-
# file, the way listTopics in src/docs/read.ts sorts both together.
|
|
87
|
-
#
|
|
88
|
-
# It stops at the folder where listTopics goes on to name each sub-area file.
|
|
89
|
-
# This is the downstream catalog and that one answers what a caller could have
|
|
90
|
-
# typed, so a reachable name absent here is the same divergence the internal
|
|
91
|
-
# topics already carry, and `canon docs docs` states it.
|
|
92
|
-
collect_context() {
|
|
93
|
-
local file name description
|
|
94
|
-
{
|
|
95
|
-
while IFS= read -r file; do
|
|
96
|
-
name=$(basename "$file" .md)
|
|
97
|
-
[ "$name" = "index" ] && continue
|
|
98
|
-
is_internal_topic "$name" && continue
|
|
99
|
-
description=$(read_frontmatter_field "$file" "description")
|
|
100
|
-
printf '%s\t%s\t%s\n' "$name" "$description" "canon/context/$name.md"
|
|
101
|
-
done < <(find "$CONTEXT_DIR" -maxdepth 1 -type f -name "*.md")
|
|
102
|
-
|
|
103
|
-
# A split domain is named by its folder and described by its generated
|
|
104
|
-
# index, which carries subtitle where a sibling file carries description
|
|
105
|
-
while IFS= read -r file; do
|
|
106
|
-
name=$(basename "$(dirname "$file")")
|
|
107
|
-
is_internal_topic "$name" && continue
|
|
108
|
-
description=$(read_frontmatter_field "$file" "subtitle")
|
|
109
|
-
printf '%s\t%s\t%s\n' "$name" "$description" "canon/context/$name/index.md"
|
|
110
|
-
done < <(find "$CONTEXT_DIR" -mindepth 2 -maxdepth 2 -type f -name "index.md")
|
|
111
|
-
} | sort
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
list_text() {
|
|
115
|
-
local name description category target
|
|
116
|
-
log_step "Docs"
|
|
117
|
-
while IFS=$'\t' read -r name description category target; do
|
|
118
|
-
log_info "$name : $description"
|
|
119
|
-
done < <(collect_docs)
|
|
120
|
-
|
|
121
|
-
# Absent in a registry install, which ships docs/ without .claude/
|
|
122
|
-
if [ -d "$CONTEXT_DIR" ]; then
|
|
123
|
-
log_step "Domain context"
|
|
124
|
-
while IFS=$'\t' read -r name description target; do
|
|
125
|
-
log_info "$name : $description"
|
|
126
|
-
done < <(collect_context)
|
|
127
|
-
fi
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
emit_json_entry() {
|
|
131
|
-
local name="$1" description="$2" category="$3" target="$4"
|
|
132
|
-
[ "$JSON_FIRST" -eq 0 ] && printf ','
|
|
133
|
-
jq -nc \
|
|
134
|
-
--arg name "$name" \
|
|
135
|
-
--arg description "$description" \
|
|
136
|
-
--arg category "$category" \
|
|
137
|
-
--arg target "$target" \
|
|
138
|
-
'{name: $name, description: $description, category: $category, target: $target}'
|
|
139
|
-
JSON_FIRST=0
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
list_json() {
|
|
143
|
-
local name description category target
|
|
144
|
-
JSON_FIRST=1
|
|
145
|
-
printf '['
|
|
146
|
-
while IFS=$'\t' read -r name description category target; do
|
|
147
|
-
emit_json_entry "$name" "$description" "$category" "$target"
|
|
148
|
-
done < <(collect_docs)
|
|
149
|
-
|
|
150
|
-
if [ -d "$CONTEXT_DIR" ]; then
|
|
151
|
-
while IFS=$'\t' read -r name description target; do
|
|
152
|
-
emit_json_entry "$name" "$description" "" "$target"
|
|
153
|
-
done < <(collect_context)
|
|
154
|
-
fi
|
|
155
|
-
printf ']'
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
main() {
|
|
159
|
-
local json=0
|
|
160
|
-
|
|
161
|
-
while [[ $# -gt 0 ]]; do
|
|
162
|
-
case "$1" in
|
|
163
|
-
-h | --help) show_help ;;
|
|
164
|
-
--json)
|
|
165
|
-
json=1
|
|
166
|
-
shift
|
|
167
|
-
;;
|
|
168
|
-
*) log_error "Unknown option: $1" ;;
|
|
169
|
-
esac
|
|
170
|
-
done
|
|
171
|
-
|
|
172
|
-
trap close_timeline EXIT
|
|
173
|
-
|
|
174
|
-
if [ "$json" -eq 1 ]; then
|
|
175
|
-
printf '{"docs":'
|
|
176
|
-
list_json
|
|
177
|
-
printf '}\n'
|
|
178
|
-
exit 0
|
|
179
|
-
fi
|
|
180
|
-
|
|
181
|
-
list_text
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
main "$@"
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
|
|
3
|
-
# Reads one frontmatter field from a markdown file.
|
|
4
|
-
#
|
|
5
|
-
# Kept in bash because the list commands call it once per field per file
|
|
6
|
-
# inside a loop, where shelling into the CLI would cost a process per read.
|
|
7
|
-
# Index logic is TypeScript, in src/indexes/, and does not belong here.
|
|
8
|
-
read_frontmatter_field() {
|
|
9
|
-
local file="$1"
|
|
10
|
-
local key="$2"
|
|
11
|
-
awk -v key="$key" '
|
|
12
|
-
NR == 1 && /^---$/ { in_fm = 1; next }
|
|
13
|
-
in_fm && /^---$/ { exit }
|
|
14
|
-
in_fm {
|
|
15
|
-
prefix = key ": "
|
|
16
|
-
if (index($0, prefix) == 1) {
|
|
17
|
-
val = substr($0, length(prefix) + 1)
|
|
18
|
-
if (length(val) >= 2) {
|
|
19
|
-
first = substr(val, 1, 1)
|
|
20
|
-
last = substr(val, length(val), 1)
|
|
21
|
-
if ((first == "\"" && last == "\"") || (first == "'\''" && last == "'\''")) {
|
|
22
|
-
val = substr(val, 2, length(val) - 2)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
print val
|
|
26
|
-
exit
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
' "$file"
|
|
30
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
-
PROJECT_ROOT="${PROJECT_ROOT:-$(dirname "$(dirname "$SCRIPT_DIR")")}"
|
|
7
|
-
|
|
8
|
-
source "$PROJECT_ROOT/scripts/lib/ui.sh"
|
|
9
|
-
source "$PROJECT_ROOT/scripts/lib/frontmatter.sh"
|
|
10
|
-
|
|
11
|
-
STANDARDS_DIR="$PROJECT_ROOT/standards"
|
|
12
|
-
|
|
13
|
-
show_help() {
|
|
14
|
-
echo -e "${GREY}┌${NC}"
|
|
15
|
-
echo -e "${GREY}├${NC} ${WHITE}Usage:${NC} canon standards list [options]"
|
|
16
|
-
echo -e "${GREY}│${NC}"
|
|
17
|
-
echo -e "${GREY}│${NC} ${WHITE}Options:${NC}"
|
|
18
|
-
echo -e "${GREY}│${NC} --json ${GREY}# Emit machine-readable JSON${NC}"
|
|
19
|
-
echo -e "${GREY}│${NC} -h, --help ${GREY}# Show this help message${NC}"
|
|
20
|
-
echo -e "${GREY}└${NC}"
|
|
21
|
-
exit 0
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
json_escape() {
|
|
25
|
-
local s="$1"
|
|
26
|
-
s="${s//\\/\\\\}"
|
|
27
|
-
s="${s//\"/\\\"}"
|
|
28
|
-
printf '%s' "$s"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Emits the paths a standard's `## Scope` statement declares, as a JSON array.
|
|
32
|
-
# The declaration is prose, so the contract is narrow on purpose: backticked
|
|
33
|
-
# paths in the first sentence of the statement, or `*` for an attribute standard,
|
|
34
|
-
# which names no path and says so in the same statement. An empty array means the
|
|
35
|
-
# statement did not parse, which consumers report rather than skip.
|
|
36
|
-
read_applies_to() {
|
|
37
|
-
local file="$1"
|
|
38
|
-
local scope first paths
|
|
39
|
-
|
|
40
|
-
scope=$(awk '/^## Scope[[:space:]]*$/ {found = 1; next} found && NF {print; exit}' "$file")
|
|
41
|
-
first="${scope%%. *}"
|
|
42
|
-
paths=$(printf '%s' "$first" | grep -o '`[^`]*`' | tr -d '`' || true)
|
|
43
|
-
|
|
44
|
-
if [ -z "$paths" ] && [[ "$scope" == *"attribute standard"* ]]; then
|
|
45
|
-
paths="*"
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
printf '%s\n' "$paths" | jq -Rnc '[inputs | select(length > 0)]'
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
list_text() {
|
|
52
|
-
log_step "Standards"
|
|
53
|
-
local file name title
|
|
54
|
-
while IFS= read -r file; do
|
|
55
|
-
name=$(basename "$file" .md)
|
|
56
|
-
[ "$name" = "index" ] && continue
|
|
57
|
-
title=$(read_frontmatter_field "$file" "description")
|
|
58
|
-
log_info "$name : $title"
|
|
59
|
-
done < <(find "$STANDARDS_DIR" -maxdepth 1 -type f -name "*.md" | sort)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
# No `target` field. It named where a standard installed, and nothing installs
|
|
63
|
-
# one now, so every value it could carry is either a path only this repository
|
|
64
|
-
# has or a duplicate of what `canon standards <name>` reports. `content` already
|
|
65
|
-
# carries the document, which is what a consumer wanted the path for.
|
|
66
|
-
list_json() {
|
|
67
|
-
local first=1
|
|
68
|
-
local file name title applies_to
|
|
69
|
-
printf '['
|
|
70
|
-
while IFS= read -r file; do
|
|
71
|
-
name=$(basename "$file" .md)
|
|
72
|
-
[ "$name" = "index" ] && continue
|
|
73
|
-
title=$(read_frontmatter_field "$file" "description")
|
|
74
|
-
applies_to=$(read_applies_to "$file")
|
|
75
|
-
if [ "$first" -eq 0 ]; then
|
|
76
|
-
printf ','
|
|
77
|
-
fi
|
|
78
|
-
jq -nc \
|
|
79
|
-
--arg name "$name" \
|
|
80
|
-
--arg description "$title" \
|
|
81
|
-
--argjson appliesTo "$applies_to" \
|
|
82
|
-
--rawfile content "$file" \
|
|
83
|
-
'{name: $name, description: $description, appliesTo: $appliesTo, content: $content}'
|
|
84
|
-
first=0
|
|
85
|
-
done < <(find "$STANDARDS_DIR" -maxdepth 1 -type f -name "*.md" | sort)
|
|
86
|
-
printf ']'
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
main() {
|
|
90
|
-
local json=0
|
|
91
|
-
|
|
92
|
-
while [[ $# -gt 0 ]]; do
|
|
93
|
-
case "$1" in
|
|
94
|
-
-h | --help) show_help ;;
|
|
95
|
-
--json)
|
|
96
|
-
json=1
|
|
97
|
-
shift
|
|
98
|
-
;;
|
|
99
|
-
*) log_error "Unknown option: $1" ;;
|
|
100
|
-
esac
|
|
101
|
-
done
|
|
102
|
-
|
|
103
|
-
trap close_timeline EXIT
|
|
104
|
-
|
|
105
|
-
if [ "$json" -eq 1 ]; then
|
|
106
|
-
printf '{"standards":'
|
|
107
|
-
list_json
|
|
108
|
-
printf '}\n'
|
|
109
|
-
exit 0
|
|
110
|
-
fi
|
|
111
|
-
|
|
112
|
-
list_text
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
main "$@"
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import type { Command } from 'commander'
|
|
2
|
-
import { execScript } from '@/exec'
|
|
3
|
-
import { intro } from '@/ui'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Registers the verbs a domain has not migrated yet, each forwarding to
|
|
7
|
-
* `scripts/<domain>/<verb>.sh`. Naming them instead of catching everything on
|
|
8
|
-
* the parent gives each verb its own `--help` and lets a migrated sibling sit
|
|
9
|
-
* alongside them.
|
|
10
|
-
*/
|
|
11
|
-
export function registerPassThroughVerbs(
|
|
12
|
-
parent: Command,
|
|
13
|
-
domain: string,
|
|
14
|
-
verbs: readonly string[],
|
|
15
|
-
): void {
|
|
16
|
-
for (const verb of verbs) {
|
|
17
|
-
parent
|
|
18
|
-
.command(verb)
|
|
19
|
-
.description(`Run the ${domain} ${verb} command`)
|
|
20
|
-
.allowUnknownOption()
|
|
21
|
-
.allowExcessArguments(true)
|
|
22
|
-
.passThroughOptions()
|
|
23
|
-
.helpOption(false)
|
|
24
|
-
.action(async (_opts: unknown, cmd: Command) => {
|
|
25
|
-
if (!isHelpRequest(cmd.args)) intro(`canon ${domain}`)
|
|
26
|
-
await execScript(`${domain}/${verb}.sh`, cmd.args)
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Commander resolves `--help` before an action runs, so a pass-through verb
|
|
33
|
-
* that keeps the built-in help option prints a one-line stub instead of the
|
|
34
|
-
* script's usage. Disabling it lets the flag reach the script, which owns the
|
|
35
|
-
* real flag surface, and this guard keeps the frame from opening around it.
|
|
36
|
-
*/
|
|
37
|
-
function isHelpRequest(args: readonly string[]): boolean {
|
|
38
|
-
return args.includes('-h') || args.includes('--help')
|
|
39
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
GREEN='\033[0;32m'
|
|
6
|
-
RED='\033[0;31m'
|
|
7
|
-
WHITE='\033[1;37m'
|
|
8
|
-
GREY='\033[0;90m'
|
|
9
|
-
NC='\033[0m'
|
|
10
|
-
|
|
11
|
-
NESTED="${VERIFY_NESTED:-false}"
|
|
12
|
-
|
|
13
|
-
log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1"; }
|
|
14
|
-
log_error() {
|
|
15
|
-
echo -e "${GREY}│${NC} ${RED}✗${NC} $1"
|
|
16
|
-
exit 1
|
|
17
|
-
}
|
|
18
|
-
log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}"; }
|
|
19
|
-
log_skip() { echo -e "${GREY}│ - $1${NC}"; }
|
|
20
|
-
|
|
21
|
-
pipe_output() { while IFS= read -r line; do echo -e "${GREY}│${NC} $line"; done; }
|
|
22
|
-
|
|
23
|
-
check_dependencies() {
|
|
24
|
-
command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
|
|
25
|
-
command -v go >/dev/null 2>&1 || log_error "go is not installed"
|
|
26
|
-
command -v golangci-lint >/dev/null 2>&1 || log_error "golangci-lint is not installed. Run 'go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest'"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
check_package() {
|
|
30
|
-
[ -f package.json ] || log_error "No package.json here. Run 'bun init', then sync the stack again"
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
has_script() {
|
|
34
|
-
bun -e 'process.exit(require(process.cwd() + "/package.json").scripts?.[process.argv[1]] ? 0 : 1)' "$1"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
# A subfolder synced with --skip base declares none of base's scripts, since
|
|
38
|
-
# the repository root owns them. Each base phase runs only where its script is
|
|
39
|
-
# declared, so this script passes on its own in that subfolder.
|
|
40
|
-
run_base_phase() {
|
|
41
|
-
local title=$1
|
|
42
|
-
local script=$2
|
|
43
|
-
local err_msg=$3
|
|
44
|
-
local pass_msg=$4
|
|
45
|
-
|
|
46
|
-
log_step "$title"
|
|
47
|
-
if ! has_script "$script"; then
|
|
48
|
-
log_skip "Skipped: $script is not declared here, so the repository root runs it"
|
|
49
|
-
return
|
|
50
|
-
fi
|
|
51
|
-
run_check "bun run $script" "$err_msg"
|
|
52
|
-
log_info "$pass_msg"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
check_markdown_bans() {
|
|
56
|
-
if ! command -v canon >/dev/null 2>&1; then
|
|
57
|
-
log_info "Skipped: no canon binary on PATH. Install with \`bun install --global @erclx/canon\`."
|
|
58
|
-
return 0
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
local files=()
|
|
62
|
-
while IFS= read -r file; do
|
|
63
|
-
files+=("$file")
|
|
64
|
-
done < <(git ls-files --cached --others --exclude-standard -- '*.md' ':(exclude)CHANGELOG.md' ':(exclude)**/CHANGELOG.md')
|
|
65
|
-
|
|
66
|
-
if [ ${#files[@]} -eq 0 ]; then
|
|
67
|
-
log_info "No markdown file to check outside the exclusion set."
|
|
68
|
-
return 0
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
local output code
|
|
72
|
-
if output=$(canon markdown audit "${files[@]}" 2>&1); then
|
|
73
|
-
code=0
|
|
74
|
-
else
|
|
75
|
-
code=$?
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
case "$code" in
|
|
79
|
-
0)
|
|
80
|
-
log_info "No banned character"
|
|
81
|
-
;;
|
|
82
|
-
1)
|
|
83
|
-
log_info "Skipped: canon markdown audit refused and measured nothing."
|
|
84
|
-
;;
|
|
85
|
-
2)
|
|
86
|
-
echo "$output" | pipe_output
|
|
87
|
-
log_error "Markdown prose carries a banned character, or a relative link resolves to nothing on disk"
|
|
88
|
-
;;
|
|
89
|
-
3)
|
|
90
|
-
echo "$output" | pipe_output
|
|
91
|
-
log_error "The markdown audit shipped an empty ban set, so the corpus was walked and nothing was looked for"
|
|
92
|
-
;;
|
|
93
|
-
*)
|
|
94
|
-
log_error "canon markdown audit exited $code, which is neither a pass nor a finding"
|
|
95
|
-
;;
|
|
96
|
-
esac
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
run_check() {
|
|
100
|
-
local cmd=$1
|
|
101
|
-
local err_msg=$2
|
|
102
|
-
local output
|
|
103
|
-
if ! output=$(eval "$cmd" 2>&1); then
|
|
104
|
-
echo "$output" | pipe_output
|
|
105
|
-
log_error "$err_msg"
|
|
106
|
-
fi
|
|
107
|
-
echo "$output" | pipe_output
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
main() {
|
|
111
|
-
check_dependencies
|
|
112
|
-
check_package
|
|
113
|
-
|
|
114
|
-
if [ "$NESTED" = false ]; then echo -e "${GREY}┌${NC}"; fi
|
|
115
|
-
|
|
116
|
-
echo -e "${GREY}├${NC} ${WHITE}Typecheck${NC}"
|
|
117
|
-
run_check "bun run typecheck" "Typecheck failed"
|
|
118
|
-
log_info "Typecheck passed"
|
|
119
|
-
|
|
120
|
-
log_step "Lint"
|
|
121
|
-
run_check "bun run lint" "Lint failed"
|
|
122
|
-
log_info "Lint passed"
|
|
123
|
-
|
|
124
|
-
run_base_phase "Formatting" "format" "Format failed" "Format applied"
|
|
125
|
-
run_base_phase "Format check" "check:format" "Format check failed" "Format check passed"
|
|
126
|
-
run_base_phase "Spelling" "check:spell" "Spell check failed" "Spell check passed"
|
|
127
|
-
run_base_phase "Shell" "check:shell" "Shell check failed" "Shell check passed"
|
|
128
|
-
|
|
129
|
-
log_step "Unit tests"
|
|
130
|
-
run_check "bun run test:run" "Unit tests failed"
|
|
131
|
-
log_info "Unit tests passed"
|
|
132
|
-
|
|
133
|
-
log_step "Markdown bans"
|
|
134
|
-
check_markdown_bans
|
|
135
|
-
|
|
136
|
-
if [ "$NESTED" = false ]; then
|
|
137
|
-
echo -e "${GREY}└${NC}\n"
|
|
138
|
-
echo -e "${GREEN}✓ Verification passed${NC}"
|
|
139
|
-
fi
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
main "$@"
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
set -o pipefail
|
|
4
|
-
|
|
5
|
-
GREEN='\033[0;32m'
|
|
6
|
-
RED='\033[0;31m'
|
|
7
|
-
WHITE='\033[1;37m'
|
|
8
|
-
GREY='\033[0;90m'
|
|
9
|
-
NC='\033[0m'
|
|
10
|
-
|
|
11
|
-
NESTED="${VERIFY_NESTED:-false}"
|
|
12
|
-
|
|
13
|
-
log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1"; }
|
|
14
|
-
log_error() {
|
|
15
|
-
echo -e "${GREY}│${NC} ${RED}✗${NC} $1"
|
|
16
|
-
exit 1
|
|
17
|
-
}
|
|
18
|
-
log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}"; }
|
|
19
|
-
log_skip() { echo -e "${GREY}│ - $1${NC}"; }
|
|
20
|
-
|
|
21
|
-
pipe_output() { while IFS= read -r line; do echo -e "${GREY}│${NC} $line"; done; }
|
|
22
|
-
|
|
23
|
-
check_dependencies() {
|
|
24
|
-
command -v bun >/dev/null 2>&1 || log_error "bun is not installed"
|
|
25
|
-
command -v php >/dev/null 2>&1 || log_error "php is not installed"
|
|
26
|
-
command -v composer >/dev/null 2>&1 || log_error "composer is not installed"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
check_package() {
|
|
30
|
-
[ -f package.json ] || log_error "No package.json here. Run 'bun init', then sync the stack again"
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
check_vendor() {
|
|
34
|
-
[ -x vendor/bin/phpunit ] || log_error "No vendor/bin/phpunit here. Run 'composer install'"
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
has_script() {
|
|
38
|
-
bun -e 'process.exit(require(process.cwd() + "/package.json").scripts?.[process.argv[1]] ? 0 : 1)' "$1"
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# A subfolder synced with --skip base declares none of base's scripts, since
|
|
42
|
-
# the repository root owns them. Each base phase runs only where its script is
|
|
43
|
-
# declared, so this script passes on its own in that subfolder.
|
|
44
|
-
run_base_phase() {
|
|
45
|
-
local title=$1
|
|
46
|
-
local script=$2
|
|
47
|
-
local err_msg=$3
|
|
48
|
-
local pass_msg=$4
|
|
49
|
-
|
|
50
|
-
log_step "$title"
|
|
51
|
-
if ! has_script "$script"; then
|
|
52
|
-
log_skip "Skipped: $script is not declared here, so the repository root runs it"
|
|
53
|
-
return
|
|
54
|
-
fi
|
|
55
|
-
run_check "bun run $script" "$err_msg"
|
|
56
|
-
log_info "$pass_msg"
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
check_markdown_bans() {
|
|
60
|
-
if ! command -v canon >/dev/null 2>&1; then
|
|
61
|
-
log_info "Skipped: no canon binary on PATH. Install with \`bun install --global @erclx/canon\`."
|
|
62
|
-
return 0
|
|
63
|
-
fi
|
|
64
|
-
|
|
65
|
-
local files=()
|
|
66
|
-
while IFS= read -r file; do
|
|
67
|
-
files+=("$file")
|
|
68
|
-
done < <(git ls-files --cached --others --exclude-standard -- '*.md' ':(exclude)CHANGELOG.md' ':(exclude)**/CHANGELOG.md')
|
|
69
|
-
|
|
70
|
-
if [ ${#files[@]} -eq 0 ]; then
|
|
71
|
-
log_info "No markdown file to check outside the exclusion set."
|
|
72
|
-
return 0
|
|
73
|
-
fi
|
|
74
|
-
|
|
75
|
-
local output code
|
|
76
|
-
if output=$(canon markdown audit "${files[@]}" 2>&1); then
|
|
77
|
-
code=0
|
|
78
|
-
else
|
|
79
|
-
code=$?
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
case "$code" in
|
|
83
|
-
0)
|
|
84
|
-
log_info "No banned character"
|
|
85
|
-
;;
|
|
86
|
-
1)
|
|
87
|
-
log_info "Skipped: canon markdown audit refused and measured nothing."
|
|
88
|
-
;;
|
|
89
|
-
2)
|
|
90
|
-
echo "$output" | pipe_output
|
|
91
|
-
log_error "Markdown prose carries a banned character, or a relative link resolves to nothing on disk"
|
|
92
|
-
;;
|
|
93
|
-
3)
|
|
94
|
-
echo "$output" | pipe_output
|
|
95
|
-
log_error "The markdown audit shipped an empty ban set, so the corpus was walked and nothing was looked for"
|
|
96
|
-
;;
|
|
97
|
-
*)
|
|
98
|
-
log_error "canon markdown audit exited $code, which is neither a pass nor a finding"
|
|
99
|
-
;;
|
|
100
|
-
esac
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
run_check() {
|
|
104
|
-
local cmd=$1
|
|
105
|
-
local err_msg=$2
|
|
106
|
-
local output
|
|
107
|
-
if ! output=$(eval "$cmd" 2>&1); then
|
|
108
|
-
echo "$output" | pipe_output
|
|
109
|
-
log_error "$err_msg"
|
|
110
|
-
fi
|
|
111
|
-
echo "$output" | pipe_output
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
main() {
|
|
115
|
-
check_dependencies
|
|
116
|
-
check_package
|
|
117
|
-
check_vendor
|
|
118
|
-
|
|
119
|
-
if [ "$NESTED" = false ]; then echo -e "${GREY}┌${NC}"; fi
|
|
120
|
-
|
|
121
|
-
echo -e "${GREY}├${NC} ${WHITE}Typecheck${NC}"
|
|
122
|
-
run_check "bun run typecheck" "Typecheck failed"
|
|
123
|
-
log_info "Typecheck passed"
|
|
124
|
-
|
|
125
|
-
log_step "Lint"
|
|
126
|
-
run_check "bun run lint" "Lint failed"
|
|
127
|
-
log_info "Lint passed"
|
|
128
|
-
|
|
129
|
-
run_base_phase "Formatting" "format" "Format failed" "Format applied"
|
|
130
|
-
run_base_phase "Format check" "check:format" "Format check failed" "Format check passed"
|
|
131
|
-
run_base_phase "Spelling" "check:spell" "Spell check failed" "Spell check passed"
|
|
132
|
-
run_base_phase "Shell" "check:shell" "Shell check failed" "Shell check passed"
|
|
133
|
-
|
|
134
|
-
log_step "Unit tests"
|
|
135
|
-
run_check "bun run test:run" "Unit tests failed"
|
|
136
|
-
log_info "Unit tests passed"
|
|
137
|
-
|
|
138
|
-
log_step "Markdown bans"
|
|
139
|
-
check_markdown_bans
|
|
140
|
-
|
|
141
|
-
if [ "$NESTED" = false ]; then
|
|
142
|
-
echo -e "${GREY}└${NC}\n"
|
|
143
|
-
echo -e "${GREEN}✓ Verification passed${NC}"
|
|
144
|
-
fi
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
main "$@"
|