@codedrifters/configulator 0.0.272 → 0.0.273

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/lib/index.mjs CHANGED
@@ -8737,6 +8737,295 @@ var extractApiProcedure = {
8737
8737
  description: "Run @microsoft/api-extractor for a single package and write the .api.md rollup to the scratch folder declared by the package's api-extractor.json. Rollups are regenerate-on-scan per docs-sync epic #448 resolved decision #3 \u2014 never committed to git.",
8738
8738
  content: renderExtractApiProcedure()
8739
8739
  };
8740
+ function renderCheckLinksProcedure() {
8741
+ return [
8742
+ "#!/usr/bin/env bash",
8743
+ "# check-links.sh \u2014 Link integrity checker for the Starlight docs site.",
8744
+ "#",
8745
+ "# Usage:",
8746
+ "# .claude/procedures/check-links.sh <mode> <docs-root> [<site-dir>]",
8747
+ "#",
8748
+ "# Where:",
8749
+ "# <mode> \u2014 one of `internal`, `external`, or `all`.",
8750
+ "# <docs-root> \u2014 path to the markdown content tree",
8751
+ "# (e.g. `docs/src/content/docs`). Walked recursively",
8752
+ "# for `*.md` / `*.mdx` files.",
8753
+ "# <site-dir> \u2014 (optional, internal/all only) path to the Starlight",
8754
+ "# project root that contains `astro.config.{ts,mjs,js}`.",
8755
+ "# Defaults to two levels up from `<docs-root>` so the",
8756
+ "# standard Starlight layout (`<site>/src/content/docs`)",
8757
+ "# resolves automatically.",
8758
+ "#",
8759
+ "# Wraps two external tools:",
8760
+ "# - `astro check` \u2014 internal links and content-collection refs",
8761
+ "# inside the Starlight site.",
8762
+ "# - `lychee` \u2014 external `https://\u2026` URLs across the docs tree",
8763
+ "# (https://github.com/lycheeverse/lychee).",
8764
+ "#",
8765
+ "# Output (stdout, single JSON array):",
8766
+ "#",
8767
+ "# [",
8768
+ "# {",
8769
+ '# "url": "<href as written in the markdown source>",',
8770
+ '# "docPath": "<path relative to <docs-root>>",',
8771
+ '# "line": <1-indexed line number>,',
8772
+ '# "kind": "internal" | "external",',
8773
+ '# "reason": "<short tool-supplied diagnostic>"',
8774
+ "# },",
8775
+ "# ...",
8776
+ "# ]",
8777
+ "#",
8778
+ "# An empty array (`[]`) is emitted when no broken links are found.",
8779
+ "# Detection is **data**, not failure: this helper exits 0 when a",
8780
+ "# tool ran successfully and reported zero or more broken links.",
8781
+ "# The downstream docs-sync scan phase (#519/#520) is responsible",
8782
+ "# for deciding which findings are advisory and which block.",
8783
+ "#",
8784
+ "# Exit codes:",
8785
+ "# 0 \u2014 every requested tool ran successfully; findings (zero or",
8786
+ "# more) are on stdout as a JSON array.",
8787
+ "# 1 \u2014 usage error (missing args, unreadable docs root).",
8788
+ "# 2 \u2014 a required external tool is not installed or not on PATH",
8789
+ "# (`astro` / `lychee` / `node` / `jq`).",
8790
+ "# 3 \u2014 a tool ran but exited non-zero for a reason other than",
8791
+ "# broken-link detection (config error, IO error, network",
8792
+ "# failure for lychee, etc.). Stderr carries the diagnostic.",
8793
+ "#",
8794
+ "# The script never edits files. Diagnostics flow to stderr; the",
8795
+ "# only thing on stdout is the normalized JSON array.",
8796
+ "",
8797
+ "set -uo pipefail",
8798
+ "",
8799
+ "err() {",
8800
+ ' printf "check-links.sh: %s\\n" "$*" >&2',
8801
+ "}",
8802
+ "",
8803
+ 'if [ "$#" -lt 2 ]; then',
8804
+ ' err "usage: check-links.sh <internal|external|all> <docs-root> [<site-dir>]"',
8805
+ " exit 1",
8806
+ "fi",
8807
+ "",
8808
+ 'mode="$1"',
8809
+ 'docs_root="$2"',
8810
+ 'site_dir="${3:-}"',
8811
+ "",
8812
+ 'case "$mode" in',
8813
+ " internal | external | all) ;;",
8814
+ " *)",
8815
+ ` err "unknown mode '$mode' \u2014 expected 'internal', 'external', or 'all'"`,
8816
+ " exit 1",
8817
+ " ;;",
8818
+ "esac",
8819
+ "",
8820
+ 'if [ ! -d "$docs_root" ]; then',
8821
+ ' err "docs root not found: $docs_root"',
8822
+ " exit 1",
8823
+ "fi",
8824
+ "",
8825
+ 'if [ -z "$site_dir" ]; then',
8826
+ " # Standard Starlight layout: <site>/src/content/docs. Walk two",
8827
+ " # parents up from the content root to recover the site dir.",
8828
+ ' site_dir="$(cd "$docs_root/../../.." && pwd)"',
8829
+ "fi",
8830
+ "",
8831
+ "# `jq` is required to merge the per-tool JSON output streams. Fail",
8832
+ "# fast with the dedicated tool-missing exit code if it is absent.",
8833
+ "if ! command -v jq >/dev/null 2>&1; then",
8834
+ ' err "jq is required but not on PATH"',
8835
+ " exit 2",
8836
+ "fi",
8837
+ "",
8838
+ 'tmp_dir="$(mktemp -d -t check-links-XXXXXX)"',
8839
+ "# shellcheck disable=SC2064",
8840
+ `trap "rm -rf '$tmp_dir'" EXIT`,
8841
+ "",
8842
+ 'internal_json="$tmp_dir/internal.json"',
8843
+ 'external_json="$tmp_dir/external.json"',
8844
+ 'printf "[]" > "$internal_json"',
8845
+ 'printf "[]" > "$external_json"',
8846
+ "",
8847
+ "# ---------------------------------------------------------------",
8848
+ "# Internal-link check",
8849
+ "# ---------------------------------------------------------------",
8850
+ "# `astro check` runs inside the Starlight project root and walks",
8851
+ "# content collections to validate internal references. It exits",
8852
+ "# non-zero when references are broken \u2014 that is a *finding* for",
8853
+ "# this helper, not a tool failure, so we capture stderr and parse",
8854
+ "# it into the normalized record shape rather than propagating the",
8855
+ "# non-zero exit.",
8856
+ 'if [ "$mode" = "internal" ] || [ "$mode" = "all" ]; then',
8857
+ " if ! command -v node >/dev/null 2>&1; then",
8858
+ ' err "node is required but not on PATH"',
8859
+ " exit 2",
8860
+ " fi",
8861
+ "",
8862
+ ' if [ ! -d "$site_dir" ]; then',
8863
+ ' err "site dir not found: $site_dir"',
8864
+ " exit 1",
8865
+ " fi",
8866
+ "",
8867
+ " # Locate the astro binary in the site's local node_modules first",
8868
+ " # so the version matches what `pnpm install` resolved for the",
8869
+ " # Starlight site. Fall back to a workspace-aware `pnpm exec`.",
8870
+ ' if [ -x "$site_dir/node_modules/.bin/astro" ]; then',
8871
+ ' astro_bin=("$site_dir/node_modules/.bin/astro")',
8872
+ " elif command -v pnpm >/dev/null 2>&1; then",
8873
+ ' astro_bin=(pnpm --dir "$site_dir" exec astro)',
8874
+ " else",
8875
+ ' err "astro binary not found in $site_dir/node_modules/.bin and pnpm is unavailable"',
8876
+ " exit 2",
8877
+ " fi",
8878
+ "",
8879
+ ' astro_log="$tmp_dir/astro.log"',
8880
+ " set +e",
8881
+ ' ( cd "$site_dir" && "${astro_bin[@]}" check ) > "$astro_log" 2>&1',
8882
+ " astro_status=$?",
8883
+ " set -e",
8884
+ "",
8885
+ " # `astro check` exits 1 on findings and >=2 on tool failure.",
8886
+ " # Translate to the normalized record stream regardless of exit.",
8887
+ ' if [ "$astro_status" -ge 2 ]; then',
8888
+ ' err "astro check exited with status $astro_status (tool failure)"',
8889
+ ' cat "$astro_log" >&2 || true',
8890
+ " exit 3",
8891
+ " fi",
8892
+ "",
8893
+ " # Parse the line-oriented log into JSON records. The `astro",
8894
+ " # check` output for a broken reference looks like:",
8895
+ " #",
8896
+ " # src/content/docs/foo.md:42:8 - error <code>: <reason>",
8897
+ " #",
8898
+ " # The helper extracts `<docPath>`, `<line>`, and `<reason>` and",
8899
+ " # synthesises a `url` field by quoting the offending excerpt",
8900
+ " # when astro reports one (it does not always); when no excerpt",
8901
+ " # is available we fall back to an empty string so the schema",
8902
+ " # stays uniform.",
8903
+ ` awk -v docs_root="$docs_root" -v site_dir="$site_dir" '`,
8904
+ " BEGIN {",
8905
+ ' printf "[";',
8906
+ " first = 1;",
8907
+ " }",
8908
+ ' # Match "<path>:<line>:<col> - <severity> <code>: <message>"',
8909
+ " /^.+:[0-9]+:[0-9]+ - / {",
8910
+ ' split($0, head, " - ");',
8911
+ ' n = split(head[1], loc, ":");',
8912
+ " file = loc[1];",
8913
+ ' for (i = 2; i < n - 1; i++) { file = file ":" loc[i]; }',
8914
+ " line = loc[n - 1];",
8915
+ ' message = "";',
8916
+ " for (i = 2; i <= length(head); i++) {",
8917
+ ' if (i > 2) message = message " - ";',
8918
+ " message = message head[i];",
8919
+ " }",
8920
+ " # Resolve the file relative to docs_root for stability.",
8921
+ " abs = file;",
8922
+ ' if (substr(abs, 1, 1) != "/") {',
8923
+ ' abs = site_dir "/" abs;',
8924
+ " }",
8925
+ ' sub(docs_root "/", "", abs);',
8926
+ ' gsub(/"/, "\\\\\\"", message);',
8927
+ ' gsub(/"/, "\\\\\\"", abs);',
8928
+ ' if (!first) printf ",";',
8929
+ " first = 0;",
8930
+ ' printf "{\\"url\\":\\"\\",\\"docPath\\":\\"%s\\",\\"line\\":%s,\\"kind\\":\\"internal\\",\\"reason\\":\\"%s\\"}", abs, line, message;',
8931
+ " }",
8932
+ " END {",
8933
+ ' printf "]";',
8934
+ " }",
8935
+ ` ' "$astro_log" > "$internal_json"`,
8936
+ "",
8937
+ " # Validate the JSON we synthesised. Catches awk escaping bugs",
8938
+ " # before we hand the payload to the merge step.",
8939
+ ' if ! jq -e . "$internal_json" >/dev/null 2>&1; then',
8940
+ ' err "failed to normalize astro check output to JSON"',
8941
+ ' cat "$internal_json" >&2 || true',
8942
+ " exit 3",
8943
+ " fi",
8944
+ "fi",
8945
+ "",
8946
+ "# ---------------------------------------------------------------",
8947
+ "# External-link check",
8948
+ "# ---------------------------------------------------------------",
8949
+ "# `lychee` walks the markdown tree and reports unreachable URLs.",
8950
+ "# Its `--format json` output is structured per-link; we project",
8951
+ "# only the failed entries into the normalized record shape.",
8952
+ 'if [ "$mode" = "external" ] || [ "$mode" = "all" ]; then',
8953
+ " if ! command -v lychee >/dev/null 2>&1; then",
8954
+ ' err "lychee is required but not on PATH (https://github.com/lycheeverse/lychee)"',
8955
+ " exit 2",
8956
+ " fi",
8957
+ "",
8958
+ ' lychee_json="$tmp_dir/lychee.json"',
8959
+ " set +e",
8960
+ " # `--no-progress` avoids interactive carriage-returns mangling",
8961
+ " # the JSON. `--format json` emits a single object whose `fail_map`",
8962
+ " # field lists the broken URLs grouped by source file.",
8963
+ " # `--exclude-mail` skips mailto: links (out of scope for this",
8964
+ " # helper).",
8965
+ ' lychee --no-progress --format json --exclude-mail "$docs_root" > "$lychee_json" 2>"$tmp_dir/lychee.err"',
8966
+ " lychee_status=$?",
8967
+ " set -e",
8968
+ "",
8969
+ " # lychee exits 0 when every link resolved, 2 when one or more",
8970
+ " # links failed. Anything else is a tool failure (config, IO).",
8971
+ ' if [ "$lychee_status" -ne 0 ] && [ "$lychee_status" -ne 2 ]; then',
8972
+ ' err "lychee exited with status $lychee_status (tool failure)"',
8973
+ ' cat "$tmp_dir/lychee.err" >&2 || true',
8974
+ " exit 3",
8975
+ " fi",
8976
+ "",
8977
+ " # Project lychee's `fail_map` into the normalized record shape.",
8978
+ ' # The `fail_map` is `{ "<source-path>": [ { url, status: { details? } }, ... ] }`.',
8979
+ " # lychee does not report a line number for every match, so we",
8980
+ " # default to 0 when it is missing (downstream consumers treat",
8981
+ ' # 0 as "unknown line").',
8982
+ ` jq --arg root "$docs_root" '`,
8983
+ " def trim_root($p; $r):",
8984
+ ' if ($p | startswith($r + "/")) then ($p | ltrimstr($r + "/"))',
8985
+ ' elif ($p == $r) then ""',
8986
+ " else $p end;",
8987
+ " [",
8988
+ " ( .fail_map // {} ) as $map",
8989
+ " | $map | to_entries[]",
8990
+ " | .key as $src",
8991
+ " | .value[]",
8992
+ " | {",
8993
+ ' url: (.url // ""),',
8994
+ " docPath: trim_root($src; $root),",
8995
+ " line: ((.line // 0) | tonumber? // 0),",
8996
+ ' kind: "external",',
8997
+ ' reason: ( (.status.details // .status.text // .status.code // "broken") | tostring )',
8998
+ " }",
8999
+ " ]",
9000
+ ` ' "$lychee_json" > "$external_json" 2>"$tmp_dir/lychee.parse.err"`,
9001
+ "",
9002
+ ' if ! jq -e . "$external_json" >/dev/null 2>&1; then',
9003
+ ' err "failed to parse lychee JSON output"',
9004
+ ' cat "$tmp_dir/lychee.parse.err" >&2 || true',
9005
+ ' cat "$lychee_json" >&2 || true',
9006
+ " exit 3",
9007
+ " fi",
9008
+ "fi",
9009
+ "",
9010
+ "# ---------------------------------------------------------------",
9011
+ "# Merge & emit",
9012
+ "# ---------------------------------------------------------------",
9013
+ "# Sort records deterministically (kind, docPath, line, url) so",
9014
+ "# downstream consumers can compare reports across runs without",
9015
+ "# fighting platform-specific ordering noise.",
9016
+ "jq -s '",
9017
+ " add",
9018
+ " | sort_by(.kind, .docPath, .line, .url)",
9019
+ `' "$internal_json" "$external_json"`,
9020
+ "",
9021
+ "exit 0"
9022
+ ].join("\n");
9023
+ }
9024
+ var checkLinksProcedure = {
9025
+ name: "check-links.sh",
9026
+ description: "Link integrity checker that wraps `astro check` (internal links) and `lychee` (external URLs) and normalizes their output into a single JSON-array stream of { url, docPath, line, kind, reason } records. Detection is data: the helper exits 0 when a tool ran successfully regardless of how many broken links it reported. Non-zero exits are reserved for tool-level failures.",
9027
+ content: renderCheckLinksProcedure()
9028
+ };
8740
9029
  function buildDocsSyncBundle(paths = DEFAULT_AGENT_PATHS) {
8741
9030
  return {
8742
9031
  name: "docs-sync",
@@ -8786,7 +9075,7 @@ function buildDocsSyncBundle(paths = DEFAULT_AGENT_PATHS) {
8786
9075
  ],
8787
9076
  skills: [buildDocsSyncPrSkill(), buildDocsSyncAuditSkill()],
8788
9077
  subAgents: [buildDocsSyncSubAgent(paths)],
8789
- procedures: [extractApiProcedure],
9078
+ procedures: [extractApiProcedure, checkLinksProcedure],
8790
9079
  labels: [
8791
9080
  {
8792
9081
  name: "type:docs-sync",
@@ -31262,6 +31551,7 @@ export {
31262
31551
  buildStandardsResearchBundle,
31263
31552
  buildUnblockDependentsProcedure,
31264
31553
  businessModelsBundle,
31554
+ checkLinksProcedure,
31265
31555
  classifyIssueScope,
31266
31556
  classifyRun,
31267
31557
  companyProfileBundle,
@@ -31285,6 +31575,7 @@ export {
31285
31575
  regulatoryResearchBundle,
31286
31576
  renderAgentTierCaseStatement,
31287
31577
  renderAgentTierSection,
31578
+ renderCheckLinksProcedure,
31288
31579
  renderCustomDocSectionBlock,
31289
31580
  renderCustomDocSections,
31290
31581
  renderExtractApiProcedure,