Skip to content

Commit 6db2b3f

Browse files
committed
ci: provision Go guide linter for factory
1 parent 358cc66 commit 6db2b3f

19 files changed

Lines changed: 184 additions & 1189 deletions

File tree

.github/workflows/factory-ci.yml

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ on:
77
- 'factory/**'
88
- '.github/workflows/guide-draft.yml'
99
- '.github/workflows/factory-ci.yml'
10-
- 'go/internal/guidecheck/**'
11-
- 'go/cmd/lint-guide/**'
10+
- 'tools/lint-guide/**'
1211
- 'FACTORY.md'
1312
- 'mise.toml'
1413
push:
@@ -18,8 +17,7 @@ on:
1817
- 'factory/**'
1918
- '.github/workflows/guide-draft.yml'
2019
- '.github/workflows/factory-ci.yml'
21-
- 'go/internal/guidecheck/**'
22-
- 'go/cmd/lint-guide/**'
20+
- 'tools/lint-guide/**'
2321
- 'FACTORY.md'
2422
- 'mise.toml'
2523

@@ -37,17 +35,24 @@ jobs:
3735

3836
- uses: actions/setup-go@v5
3937
with:
40-
go-version-file: go/go.mod
38+
go-version-file: tools/lint-guide/go.mod
39+
40+
- name: Test guide linter
41+
working-directory: tools/lint-guide
42+
run: go test ./...
43+
44+
- name: Build guide linter
45+
working-directory: tools/lint-guide
46+
run: go build -o "$RUNNER_TEMP/lint-guide" ./cmd/lint-guide
4147

4248
- name: Factory tests
49+
env:
50+
LINT_GUIDE_BIN: ${{ runner.temp }}/lint-guide
4351
run: bash factory/tests/run.sh
4452

4553
- name: Shellcheck
4654
run: shellcheck factory/scripts/*.sh factory/tests/*.sh
4755

48-
- name: Guidecheck tests
49-
working-directory: go
50-
run: go test ./internal/guidecheck ./cmd/lint-guide
5156

5257
- name: Build factory image
5358
run: |

.github/workflows/guide-draft.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ jobs:
2525
fetch-depth: 0
2626
token: ${{ secrets.AGENT_PAT || secrets.GITHUB_TOKEN }}
2727

28+
- name: Setup Go
29+
uses: actions/setup-go@v5
30+
with:
31+
go-version: '1.22'
32+
33+
- name: Build guide linter
34+
working-directory: tools/lint-guide
35+
run: go build -o "$RUNNER_TEMP/lint-guide" ./cmd/lint-guide
36+
2837
- name: Configure runner temp
2938
run: printf 'TMPDIR=%s\n' "$RUNNER_TEMP" >>"$GITHUB_ENV"
3039

@@ -125,6 +134,8 @@ jobs:
125134
- name: Validate export
126135
id: validate
127136
if: success() && steps.refusal.outcome != 'success'
137+
env:
138+
LINT_GUIDE_BIN: ${{ runner.temp }}/lint-guide
128139
run: |
129140
printf '%s\n' 'Factory export validation failed.' >"$RUNNER_TEMP/failure-reason.txt"
130141
bash factory/scripts/validate.sh "$RUNNER_TEMP/export" "$GITHUB_WORKSPACE"

docs/superpowers/specs/2026-08-27-markdown-url-placement-go-port-design.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,28 @@ Rewrite PR #171 so its authoritative guide validator is implemented in Go rather
66

77
This port removes the TypeScript guide-linter implementation and linter-only npm dependencies. It does not claim to remove Node or TypeScript from the whole repository: drafting, orchestration, stale sweep, and other pipeline commands remain separate future migrations.
88

9+
## Reconciliation After the Factory Prerequisite
10+
11+
The prerequisite factory work merged before this branch was finalized and removed the
12+
remaining TypeScript drafting pipeline. Reconciliation with current `main` therefore
13+
replaces the temporary TypeScript adapter described below with direct factory consumers;
14+
the adapter sections remain the approved pre-reconciliation rationale, not the final tree.
15+
Node 24 remains pinned for repository JavaScript tooling, while the prerequisite removed
16+
the TypeScript 7 pipeline rather than this port reintroducing obsolete code.
17+
18+
`tools/lint-guide` remains the sole semantic implementation. Factory CI tests and builds
19+
that nested module. The production guide-draft workflow installs Go 1.22, builds the
20+
command once to `${{ runner.temp }}/lint-guide`, and exports `LINT_GUIDE_BIN` at job scope.
21+
`factory/scripts/validate.sh` consumes that binary and falls back to building the same
22+
nested command for local use. The factory image also builds the nested command and copies
23+
only the static executable into the runtime image. The public `go` module contains only
24+
published API and generation code; duplicate checker and CLI packages are removed.
25+
26+
The factory's partial-export contract adds `--meta-only` without changing the original
27+
human/JSON target, ordering, or `0`/`1`/`2` contracts. The factory coordinator accepts
28+
exit `0` or `1`, validates exactly one grouped JSON guide result, and flattens only its
29+
`findings`; exit `2` or malformed output is operational failure.
30+
931
## Module Boundary
1032

1133
Create a nested Go module at `tools/lint-guide`:

factory/Dockerfile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
FROM golang:1.22.12-bookworm@sha256:3d699e4d15d0f8f13c9195c0632a16702b8cbdece2955af1c23b37ae5d55a253 AS lint-builder
22
WORKDIR /src
3-
COPY go/go.mod go/go.sum ./
3+
COPY tools/lint-guide/go.mod tools/lint-guide/go.sum ./
44
RUN go mod download
5-
COPY go/cmd ./cmd
6-
COPY go/internal ./internal
5+
COPY tools/lint-guide/cmd ./cmd
6+
COPY tools/lint-guide/internal ./internal
77
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
88
go build -trimpath -ldflags='-s -w' -o /out/lint-guide ./cmd/lint-guide
99

factory/coordinator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ REVIEWER 1/3 — technical and source accuracy, using `doctrine/roles/technical-
4040
REVIEWER 2/3 — setup-file and doctrine fidelity, using `doctrine/roles/fidelity.md`.
4141
REVIEWER 3/3 — editorial clarity and audience fit, using `doctrine/roles/review.md` and the selected persona file.
4242

43-
Each reviewer runs in its own caught boundary with `output_schema` equal to `factory/schemas/review-findings.schema.json` and the universal one-repair protocol. The full concurrent dispatch/collection runs in an enclosing caught boundary. Run the shell/linter in its own caught boundary from `/workspace`, exactly `/usr/local/bin/lint-guide --json /workspace/guides/<slug>`; never invoke `go` or `go run`. Validate parsed linter JSON before use. A completed review wave means valid output from all 3 reviewers plus a successfully parsed linter result. A failed reviewer output, malformed output after repair, linter failure, or invalid linter JSON fails the wave and must not complete the wave and therefore do not increment `review_rounds`; it selects `failed` and routes to reporting.
43+
Each reviewer runs in its own caught boundary with `output_schema` equal to `factory/schemas/review-findings.schema.json` and the universal one-repair protocol. The full concurrent dispatch/collection runs in an enclosing caught boundary. Run the shell/linter in its own caught boundary from `/workspace`, exactly `/usr/local/bin/lint-guide --json /workspace/guides/<slug>`; never invoke `go` or `go run`. Accept linter exit `0` (clean) or `1` (findings), reject exit `2`, and validate exactly one grouped JSON result whose `guide` is `/workspace/guides/<slug>` and whose `findings` is an array; flatten only that `findings` array for review. A completed review wave means valid output from all 3 reviewers plus a successfully parsed linter result. A failed reviewer output, malformed output after repair, linter invocation failure, or invalid linter JSON fails the wave and must not complete the wave and therefore do not increment `review_rounds`; it selects `failed` and routes to reporting.
4444

4545
Only after a completed review wave increment actual `review_rounds` by one (maximum 3). Normalize semantic duplicates without dropping sources; linter blockers equal reviewer blockers. If there are no blockers, select `converged`. If blockers remain and `review_rounds < 3`, start exactly one revision in a caught boundary with all normalized findings, doctrine, current files, and the selected persona; forbid external research and outside edits. Its strict `output_schema` has only `completed` (boolean), `addressed` (array), and `disputed` (array). Apply one repair and revision completion verification, then always run a confirmatory review wave; a revision can never directly converge. Repeat while capacity remains. If the confirmatory third wave has final-round blockers, select `blocked`; do not revise again. Thus at most three review/revision rounds occur, represented by at most three complete waves, and the report records the actual count.
4646

factory/scripts/validate.sh

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ backup_dir=
1515
diff_file=
1616
untracked_file=
1717
tree_file=
18-
lint_bin=
18+
lint_bin=${LINT_GUIDE_BIN:-}
19+
lint_owned=false
1920
anchor_active=false
2021
target_displaced=false
2122
new_installed=false
@@ -60,7 +61,7 @@ cleanup_transaction() {
6061
[[ -z "$diff_file" ]] || rm -f -- "$diff_file" || true
6162
[[ -z "$untracked_file" ]] || rm -f -- "$untracked_file" || true
6263
[[ -z "$tree_file" ]] || rm -f -- "$tree_file" || true
63-
[[ -z "$lint_bin" ]] || rm -f -- "$lint_bin" || true
64+
[[ "$lint_owned" != true || -z "$lint_bin" ]] || rm -f -- "$lint_bin" || true
6465
exit "$status"
6566
}
6667
trap cleanup_transaction EXIT
@@ -161,8 +162,14 @@ validate_artifacts "$guide_dir"
161162

162163
[[ -d "$repo_root/.git" || -f "$repo_root/.git" ]] || fatal "repository root is not a Git worktree"
163164
if [[ -f "$guide_dir/meta.yaml" ]]; then
164-
lint_bin=$(mktemp) || fatal "could not create guide lint executable"
165-
(cd "$script_root/go" && go build -o "$lint_bin" ./cmd/lint-guide) || fatal "could not build guide linter"
165+
if [[ -n "$lint_bin" ]]; then
166+
[[ -x "$lint_bin" ]] || fatal "LINT_GUIDE_BIN is not executable: $lint_bin"
167+
lint_bin="$(cd "$(dirname "$lint_bin")" && pwd -P)/$(basename "$lint_bin")"
168+
else
169+
lint_bin=$(mktemp) || fatal "could not create guide lint executable"
170+
lint_owned=true
171+
(cd "$script_root/tools/lint-guide" && go build -o "$lint_bin" ./cmd/lint-guide) || fatal "could not build guide linter"
172+
fi
166173
fi
167174

168175
[[ -d "$guides_dir" && ! -L "$guides_dir" ]] || fatal "repository guides path must be a physical directory"

factory/tests/test-container.sh

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ test_dockerfile_builds_static_linter_without_go_in_final_image() {
2525
assert_contains "FROM debian:trixie-slim@sha256:d7e12182ce18b85b93007c1dedf31f2d29e01ccf3182cc4017c709b6259bc132" "$dockerfile"
2626
assert_contains "CGO_ENABLED=0" "$dockerfile"
2727
assert_contains "go build" "$dockerfile"
28+
assert_contains "COPY tools/lint-guide/go.mod tools/lint-guide/go.sum ./" "$dockerfile"
29+
assert_contains "COPY tools/lint-guide/cmd ./cmd" "$dockerfile"
30+
assert_contains "COPY tools/lint-guide/internal ./internal" "$dockerfile"
2831
assert_contains "./cmd/lint-guide" "$dockerfile"
2932
assert_contains "COPY --from=lint-builder /out/lint-guide /usr/local/bin/lint-guide" "$dockerfile"
3033
assert_contains "COPY factory/scripts/validate-report.sh /usr/local/bin/validate-report" "$dockerfile"
@@ -40,7 +43,7 @@ test_docker_context_excludes_credentials_and_keeps_build_inputs() {
4043
grep -Fqx '.git' "$ignore" || fail ".dockerignore does not exclude root .git"
4144
mkdir -p "$context/nested/.git" "$context/.worktrees/private" \
4245
"$context/.claude/worktrees/private" "$context/tools/pulse-catalog" \
43-
"$context/.tmp-run" "$context/go" "$context/factory/scripts"
46+
"$context/.tmp-run" "$context/tools/lint-guide" "$context/factory/scripts"
4447
printf '%s\n' 'gitdir: /credential-bearing/worktree' >"$context/.git"
4548
printf '%s\n' credential-bearing-metadata >"$context/nested/.git/config"
4649
printf '%s\n' secret >"$context/.worktrees/private/token"
@@ -51,8 +54,8 @@ test_docker_context_excludes_credentials_and_keeps_build_inputs() {
5154
printf '%s\n' secret >"$context/pulse-catalog.json"
5255
printf '%s\n' secret >"$context/tools/pulse-catalog/pulse-catalog.json"
5356
printf '%s\n' secret >"$context/.tmp-run/token"
54-
cp "$ROOT/go/go.mod" "$ROOT/go/go.sum" "$context/go/"
55-
cp -R "$ROOT/go/cmd" "$ROOT/go/internal" "$context/go/"
57+
cp "$ROOT/tools/lint-guide/go.mod" "$ROOT/tools/lint-guide/go.sum" "$context/tools/lint-guide/"
58+
cp -R "$ROOT/tools/lint-guide/cmd" "$ROOT/tools/lint-guide/internal" "$context/tools/lint-guide/"
5659
cp "$ROOT/factory/Dockerfile" "$ROOT/factory/config.env" "$context/factory/"
5760
cp "$ROOT/factory/scripts/validate-report.sh" \
5861
"$ROOT/factory/scripts/container-entrypoint.sh" "$context/factory/scripts/"
@@ -64,7 +67,7 @@ test_docker_context_excludes_credentials_and_keeps_build_inputs() {
6467
fail "Docker context contains local-only path: $excluded"
6568
fi
6669
done
67-
for required in go/go.mod go/go.sum go/cmd/ go/internal/ factory/Dockerfile \
70+
for required in tools/lint-guide/go.mod tools/lint-guide/go.sum tools/lint-guide/cmd/ tools/lint-guide/internal/ factory/Dockerfile \
6871
factory/config.env factory/scripts/validate-report.sh factory/scripts/container-entrypoint.sh; do
6972
grep -Fq "$required" <<<"$listing" || fail "Docker context excludes required input: $required"
7073
done

factory/tests/test-contracts.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,19 @@ test_validation_partial_backup_cleanup_failure_keeps_commit() {
487487
grep -q '^outcome<<' "$VALIDATE_TMP/outputs" || fail "partial cleanup warning suppressed GitHub outputs"
488488
}
489489

490+
test_validation_uses_supplied_linter_binary() {
491+
reset_validation_fixture
492+
make_export_report converged
493+
copy_valid_guide
494+
local supplied="$VALIDATE_TMP/supplied-lint-guide"
495+
(cd "$ROOT/tools/lint-guide" && CGO_ENABLED=0 go build -o "$supplied" ./cmd/lint-guide)
496+
export REAL_GIT REAL_CP REAL_MV REAL_RM REPO SWAPPED_GUIDES
497+
(cd "$VALIDATE_TMP" && \
498+
PATH="$VALIDATE_TMP/bin:$PATH" GITHUB_OUTPUT="$VALIDATE_TMP/outputs" \
499+
LINT_GUIDE_BIN=supplied-lint-guide "$VALIDATOR" "$EXPORT" "$REPO")
500+
[[ -x "$supplied" ]] || fail "validator removed the supplied linter binary"
501+
}
502+
490503
test_validation_rejects_preexisting_out_of_scope_diff() {
491504
reset_validation_fixture
492505
printf 'changed
@@ -515,4 +528,5 @@ test_validation_rejects_staged_nested_and_nonregular_entries
515528
test_validation_rejects_staged_artifact_mismatch
516529
test_validation_backup_cleanup_failure_warns_after_commit
517530
test_validation_partial_backup_cleanup_failure_keeps_commit
531+
test_validation_uses_supplied_linter_binary
518532
test_validation_rejects_preexisting_out_of_scope_diff

factory/tests/test-coordinator.sh

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ for phrase in \
117117
'timeout-minutes: 180' \
118118
'group: guide-draft-issue-${{ github.event.issue.number }}' \
119119
'cancel-in-progress: false' 'TMPDIR=%s\n' \
120+
'uses: actions/setup-go@v5' \
121+
"go-version: '1.22'" \
122+
'go build -o "$RUNNER_TEMP/lint-guide" ./cmd/lint-guide' \
123+
'LINT_GUIDE_BIN: ${{ runner.temp }}/lint-guide' \
120124
'contents: write' 'issues: write' 'pull-requests: write' \
121125
'factory/scripts/preflight.sh' \
122126
'factory/scripts/prepare-input.sh' \
@@ -365,11 +369,16 @@ if PATH="$workflow_tmp/bin:$PATH" GH_REPO=acme/docs ISSUE_NUMBER=42 \
365369
fi
366370
test -s "$COMMENT_STATE" || fail 'bootstrap fallback did not comment after a mutation failure'
367371

372+
# Literal workflow expressions are intentionally matched without expansion.
373+
# shellcheck disable=SC2016
368374
for phrase in \
369375
'.dockerignore' \
370376
'bash factory/tests/run.sh' \
371377
'shellcheck factory/scripts/*.sh factory/tests/*.sh' \
372-
'go test ./internal/guidecheck ./cmd/lint-guide' \
378+
'tools/lint-guide/**' \
379+
'go test ./...' \
380+
'go build -o "$RUNNER_TEMP/lint-guide" ./cmd/lint-guide' \
381+
'LINT_GUIDE_BIN: ${{ runner.temp }}/lint-guide' \
373382
'KIT_VERSION=0.1.98' \
374383
'KIT_SHA256=7d14561469ced8af21df1075a9071d04a7bad1b1c5ff90d685142d3231abae85' \
375384
'-f factory/Dockerfile .'; do

go/cmd/lint-guide/main.go

Lines changed: 0 additions & 124 deletions
This file was deleted.

0 commit comments

Comments
 (0)