@windyroad/itil 0.61.2 → 1.0.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-plugin/plugin.json +1 -1
- package/README.md +2 -14
- package/bin/{wr-itil-check-afk-accept-eligible → wr-itil-render-story-map} +2 -2
- package/bin/wr-itil-story-map-edit +51 -0
- package/bin/wr-itil-story-map-query +51 -0
- package/hooks/itil-no-implement-draft-gate.sh +19 -28
- package/lib/story-oversight.sh +154 -60
- package/package.json +2 -1
- package/scripts/check-rfc-stories-ratified.sh +15 -9
- package/scripts/detect-unratified-stories-maps.sh +15 -32
- package/scripts/mark-story-oversight-confirmed.sh +80 -31
- package/scripts/migrate-story-status-mirror.sh +7 -3
- package/scripts/reconcile-stories.sh +26 -5
- package/scripts/render-story-map.mjs +1057 -0
- package/scripts/render-story-map.sh +15 -0
- package/scripts/story-map-edit.mjs +280 -0
- package/scripts/story-map-edit.sh +10 -0
- package/scripts/story-map-query.mjs +227 -0
- package/scripts/story-map-query.sh +68 -0
- package/scripts/update-story-references-section.sh +6 -1
- package/skills/capture-rfc/SKILL.md +1 -1
- package/skills/capture-story/SKILL.md +16 -8
- package/skills/capture-story-map/SKILL.md +102 -59
- package/skills/list-stories/SKILL.md +16 -10
- package/skills/list-story-maps/SKILL.md +1 -1
- package/skills/manage-rfc/SKILL.md +1 -1
- package/skills/manage-story/SKILL.md +19 -26
- package/skills/manage-story-map/SKILL.md +19 -17
- package/skills/reconcile-stories/SKILL.md +5 -2
- package/skills/work-problems/SKILL.md +2 -2
- package/templates/story-map.css +195 -0
- package/templates/story-map.html +23 -0
- package/scripts/check-afk-accept-eligible.sh +0 -230
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# detect-unratified-stories-maps.sh — ADR-090 detector.
|
|
3
3
|
#
|
|
4
|
-
# Token-cheap detection of story maps
|
|
5
|
-
# wr-architect detect-unoversighted.sh
|
|
6
|
-
#
|
|
7
|
-
# only when it carries a `confirmed` human-oversight marker AND
|
|
8
|
-
# oversight-hash matching its current
|
|
9
|
-
# drain queue:
|
|
4
|
+
# Token-cheap detection of story maps that are NOT ratified, plus the stories
|
|
5
|
+
# those maps leave unapproved. Mirrors wr-architect detect-unoversighted.sh.
|
|
6
|
+
#
|
|
7
|
+
# A MAP is ratified only when it carries a `confirmed` human-oversight marker AND
|
|
8
|
+
# a stored oversight-hash matching its current substance, so three cases enter
|
|
9
|
+
# the drain queue:
|
|
10
10
|
# - never ratified (no marker)
|
|
11
|
-
# - drift-reopened (marker says confirmed, but
|
|
11
|
+
# - drift-reopened (marker says confirmed, but substance changed → mismatch)
|
|
12
12
|
# - legacy confirmed (confirmed but no fingerprint yet — needs one re-ratify)
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
# `
|
|
16
|
-
#
|
|
17
|
-
# listing keys on the UNION of `afk-accept:` and `oversight-basis:`, because the
|
|
18
|
-
# latter is excluded from the content hash and could otherwise be stripped to
|
|
19
|
-
# hide a machine-accepted story from this drain; `afk-accept:` stays in the hash.
|
|
20
|
-
# stdout is untouched by the flag, so the Step 2.4 drain consumer is unaffected.
|
|
14
|
+
# A STORY carries no marker at all (ADR-103). It is listed when any map in its
|
|
15
|
+
# `story-maps:` field is unratified, or when it names no map — and the remedy is
|
|
16
|
+
# always to ratify the map, never the story.
|
|
21
17
|
#
|
|
22
|
-
# Usage: detect-unratified-stories-maps.sh [
|
|
18
|
+
# Usage: detect-unratified-stories-maps.sh [STORIES_DIR=docs/stories] [MAPS_DIR=docs/story-maps]
|
|
23
19
|
# Output: one unratified artefact path per line, sorted. Empty = all ratified.
|
|
24
20
|
# Always exits 0 (detector, not a gate). Consumed by the work-problems Step 2.4
|
|
25
21
|
# oversight-unconfirmed drain.
|
|
@@ -31,12 +27,6 @@ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
|
|
|
31
27
|
# shellcheck source=/dev/null
|
|
32
28
|
source "$LIB/story-oversight.sh"
|
|
33
29
|
|
|
34
|
-
WITH_AFK=0
|
|
35
|
-
if [ "${1:-}" = "--with-afk-accepted" ]; then
|
|
36
|
-
WITH_AFK=1
|
|
37
|
-
shift
|
|
38
|
-
fi
|
|
39
|
-
|
|
40
30
|
STORIES_DIR="${1:-docs/stories}"
|
|
41
31
|
MAPS_DIR="${2:-docs/story-maps}"
|
|
42
32
|
|
|
@@ -46,7 +36,10 @@ shopt -s nullglob
|
|
|
46
36
|
if [ -d "$STORIES_DIR" ]; then
|
|
47
37
|
for f in "$STORIES_DIR"/*.md "$STORIES_DIR"/*/*.md; do
|
|
48
38
|
[ "$(basename "$f")" = "README.md" ] && continue
|
|
49
|
-
|
|
39
|
+
# ADR-103: a story's approval may be its MAP's. Using the map predicate
|
|
40
|
+
# here would report every story as unratified — stories carry no marker at
|
|
41
|
+
# all — and send the drain to ratify a surface ADR-103 removed.
|
|
42
|
+
story_is_approved "$f" "$MAPS_DIR" || echo "$f"
|
|
50
43
|
done
|
|
51
44
|
fi
|
|
52
45
|
if [ -d "$MAPS_DIR" ]; then
|
|
@@ -56,14 +49,4 @@ shopt -s nullglob
|
|
|
56
49
|
fi
|
|
57
50
|
} | sort
|
|
58
51
|
|
|
59
|
-
# ADR-101 post-hoc drain — stderr only, so the stdout path contract is preserved.
|
|
60
|
-
if [ "$WITH_AFK" = "1" ] && [ -d "$STORIES_DIR" ]; then
|
|
61
|
-
for f in "$STORIES_DIR"/*.md "$STORIES_DIR"/*/*.md; do
|
|
62
|
-
[ "$(basename "$f")" = "README.md" ] && continue
|
|
63
|
-
if oversight_declares_pure_decomposition "$f" || oversight_is_pure_decomposition "$f"; then
|
|
64
|
-
echo "AFK-ACCEPTED (awaiting post-hoc human ratification): $f" >&2
|
|
65
|
-
fi
|
|
66
|
-
done
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
52
|
exit 0
|
|
@@ -8,19 +8,15 @@
|
|
|
8
8
|
# stored fingerprint no longer matches → the artefact reads as drifted /
|
|
9
9
|
# unratified until re-ratified (ADR-090 drift-invalidation).
|
|
10
10
|
#
|
|
11
|
-
#
|
|
12
|
-
# `oversight-basis
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
# decomposition basis) and is EXCLUDED from the content hash, exactly like
|
|
16
|
-
# `oversight-hash`; the authored `afk-accept:` declaration stays INSIDE the hash.
|
|
17
|
-
# Without the flag the basis line is dropped, so re-ratifying by hand clears a
|
|
18
|
-
# stale basis rather than leaving a human-ratified story labelled AFK-accepted.
|
|
11
|
+
# ADR-103 retired the ADR-101 `--pure-decomposition` flag and its
|
|
12
|
+
# `oversight-basis:` record. Nothing here strips a stale one: the only writer
|
|
13
|
+
# that did was the markdown leg, and markdown is a story, which is now refused
|
|
14
|
+
# outright. No artefact in the corpus carries the field.
|
|
19
15
|
#
|
|
20
|
-
# Usage: mark-story-oversight-confirmed.sh
|
|
16
|
+
# Usage: mark-story-oversight-confirmed.sh <story-or-map-file>
|
|
21
17
|
# Exit: 0 = ratified; 2 = usage / file error.
|
|
22
18
|
#
|
|
23
|
-
# Authority: ADR-090, ADR-
|
|
19
|
+
# Authority: ADR-090, ADR-103. Driver: P404 Phase 2, P456. Test: mark-story-oversight-confirmed.bats.
|
|
24
20
|
set -euo pipefail
|
|
25
21
|
|
|
26
22
|
# Adopter-safe: source the shared hash lib RELATIVE TO THIS SCRIPT (P317), never
|
|
@@ -30,25 +26,85 @@ LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
|
|
|
30
26
|
# shellcheck source=/dev/null
|
|
31
27
|
source "$LIB/story-oversight.sh"
|
|
32
28
|
|
|
33
|
-
basis=""
|
|
34
|
-
if [ "${1:-}" = "--pure-decomposition" ]; then
|
|
35
|
-
basis="pure-decomposition"
|
|
36
|
-
shift
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
29
|
f="${1:-}"
|
|
40
30
|
if [ -z "$f" ]; then
|
|
41
|
-
echo "mark-story-oversight-confirmed: usage: mark-story-oversight-confirmed.sh
|
|
31
|
+
echo "mark-story-oversight-confirmed: usage: mark-story-oversight-confirmed.sh <file>" >&2
|
|
42
32
|
exit 2
|
|
43
33
|
fi
|
|
44
34
|
[ -f "$f" ] || { echo "mark-story-oversight-confirmed: file not found: $f" >&2; exit 2; }
|
|
45
35
|
|
|
36
|
+
# ADR-103: only a story MAP is ratified. A story carries no oversight marker at
|
|
37
|
+
# all — its approval is its map's. Marking a story would re-create the second
|
|
38
|
+
# approval surface ADR-103 removed, so refuse rather than silently write one.
|
|
39
|
+
# What ADR-103 removes is the STORY-tier marker, and a story is markdown. So the
|
|
40
|
+
# refusal is keyed on the encoding, not on a `docs/stories/` path substring: a
|
|
41
|
+
# caller working outside that tree — a test fixture, an adopter with a different
|
|
42
|
+
# layout — must get the same answer.
|
|
43
|
+
#
|
|
44
|
+
# Deliberately NOT keyed on the ADR-102 data island, tempting as that is. A map
|
|
45
|
+
# predating ADR-102 has no island, and an island test would lock those out of
|
|
46
|
+
# ratification entirely. Marking some unrelated .html file is a user error; a
|
|
47
|
+
# story marker is a governance hole, and only the second one is worth refusing.
|
|
48
|
+
case "$f" in
|
|
49
|
+
*.md|*.markdown)
|
|
50
|
+
echo "mark-story-oversight-confirmed: refusing to mark $f (ADR-103). Only a story MAP is ratified. A story carries no oversight marker; its approval is derived from the maps named in its \`story-maps:\` field, so ratify the map instead." >&2
|
|
51
|
+
exit 2 ;;
|
|
52
|
+
esac
|
|
53
|
+
|
|
46
54
|
h="$(oversight_content_hash "$f")"
|
|
47
55
|
tmp="$(mktemp)"
|
|
48
56
|
|
|
49
57
|
case "$f" in
|
|
50
58
|
*.html)
|
|
51
|
-
if grep -
|
|
59
|
+
if grep -qF '<script id="story-map-data"' "$f"; then
|
|
60
|
+
# ADR-102 map: the data island is the source of truth and every <meta> is
|
|
61
|
+
# GENERATED from it. Writing the marker to <meta> alone would be undone by
|
|
62
|
+
# the next render, silently destroying a human ratification — so the
|
|
63
|
+
# marker goes into the island and the <meta> follows on render.
|
|
64
|
+
#
|
|
65
|
+
# TWO passes, and the order matters. Normalising the island can move a
|
|
66
|
+
# trailing comma onto a line the marker filter does NOT exclude (removing
|
|
67
|
+
# a previously-appended key un-commas the key before it), which would
|
|
68
|
+
# change the very bytes being fingerprinted. So: normalise first, hash the
|
|
69
|
+
# normalised form, then write the hash — a marker-line-only edit, which
|
|
70
|
+
# the filter excludes, so the stored value stays valid.
|
|
71
|
+
_island_normalise() {
|
|
72
|
+
python3 - "$1" "$2" <<'PYMARK'
|
|
73
|
+
import json, pathlib, sys
|
|
74
|
+
path, digest = pathlib.Path(sys.argv[1]), sys.argv[2]
|
|
75
|
+
OPEN = '<script id="story-map-data" type="application/json">'
|
|
76
|
+
html = path.read_text()
|
|
77
|
+
start = html.index(OPEN) + len(OPEN)
|
|
78
|
+
end = html.index('</script>', start)
|
|
79
|
+
data = json.loads(html[start:end].replace('\\u003c', '<'))
|
|
80
|
+
# Marker keys FIRST, never appended: appending gives the previously-last key a
|
|
81
|
+
# trailing comma, a punctuation-only change on an unfiltered line.
|
|
82
|
+
MARKERS = ('humanOversight', 'oversightHash', 'oversightDate', 'oversightNote')
|
|
83
|
+
ordered = {'humanOversight': 'confirmed', 'oversightHash': digest}
|
|
84
|
+
for k in ('oversightDate', 'oversightNote'):
|
|
85
|
+
if k in data:
|
|
86
|
+
ordered[k] = data[k]
|
|
87
|
+
ordered.update({k: v for k, v in data.items() if k not in MARKERS})
|
|
88
|
+
island = json.dumps(ordered, indent=2, ensure_ascii=False).replace('<', '\\u003c')
|
|
89
|
+
path.write_text(html[:start] + "\n" + island + "\n " + html[end:])
|
|
90
|
+
PYMARK
|
|
91
|
+
}
|
|
92
|
+
_island_normalise "$f" "pending"
|
|
93
|
+
h="$(oversight_content_hash "$f")"
|
|
94
|
+
_island_normalise "$f" "$h"
|
|
95
|
+
# Re-render so the generated <meta> block matches the island. Prefer the
|
|
96
|
+
# sibling script over $PATH: this runs from the same package, so the
|
|
97
|
+
# renderer is always beside it even when the bin shim is not on PATH.
|
|
98
|
+
_renderer="$(cd "$(dirname "$0")" && pwd)/render-story-map.sh"
|
|
99
|
+
if [ -x "$_renderer" ]; then
|
|
100
|
+
"$_renderer" "$f" >/dev/null 2>&1 || true
|
|
101
|
+
elif command -v wr-itil-render-story-map >/dev/null 2>&1; then
|
|
102
|
+
wr-itil-render-story-map "$f" >/dev/null 2>&1 || true
|
|
103
|
+
fi
|
|
104
|
+
printf 'mark-story-oversight-confirmed: ratified %s (oversight-hash %s…)\n' "$f" "${h:0:12}" >&2
|
|
105
|
+
rm -f "$tmp"
|
|
106
|
+
exit 0
|
|
107
|
+
elif grep -qi '<head' "$f"; then
|
|
52
108
|
# Insert the two metas after the <head> tag; drop any existing marker metas.
|
|
53
109
|
awk -v H="$h" '
|
|
54
110
|
/<meta[^>]*name="(human-oversight|oversight-hash)"/ { next }
|
|
@@ -67,19 +123,12 @@ case "$f" in
|
|
|
67
123
|
fi
|
|
68
124
|
;;
|
|
69
125
|
*)
|
|
70
|
-
#
|
|
71
|
-
# the
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
print "human-oversight: confirmed"
|
|
77
|
-
print "oversight-hash: " H
|
|
78
|
-
if (B != "") print "oversight-basis: " B
|
|
79
|
-
print; done=1; infm=0; next
|
|
80
|
-
}
|
|
81
|
-
{ print }
|
|
82
|
-
' "$f" > "$tmp"
|
|
126
|
+
# The markdown-frontmatter writer that used to live here was a working
|
|
127
|
+
# writer for the story-level marker ADR-103 removed. It is deleted rather
|
|
128
|
+
# than left unreachable behind the guard above, so that a later relaxation
|
|
129
|
+
# of that guard fails loudly instead of silently restoring story markers.
|
|
130
|
+
echo "mark-story-oversight-confirmed: refusing to mark $f (ADR-103) — no recognised story-map encoding (expected a <script id=\"story-map-data\"> island, or a legacy HTML map)." >&2
|
|
131
|
+
rm -f "$tmp"; exit 2
|
|
83
132
|
;;
|
|
84
133
|
esac
|
|
85
134
|
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
# Usage: migrate-story-status-mirror.sh [<stories-dir>...] (default: docs/stories)
|
|
41
41
|
# Exit: 0 = completed (including "nothing to do"); 2 = usage / missing lib.
|
|
42
42
|
#
|
|
43
|
-
# Authority: ADR-090 (oversight fingerprint), ADR-
|
|
43
|
+
# Authority: ADR-090 (oversight fingerprint), ADR-103 (stories carry no marker).
|
|
44
44
|
# Driver: P474. Test: migrate-story-status-mirror.bats.
|
|
45
45
|
|
|
46
46
|
set -euo pipefail
|
|
@@ -105,8 +105,12 @@ for d in "${dirs[@]}"; do
|
|
|
105
105
|
|
|
106
106
|
# Was this artefact ratified-and-valid BEFORE we touch it? Decide now: once
|
|
107
107
|
# the line is gone the pre-migration hash is unreproducible.
|
|
108
|
+
#
|
|
109
|
+
# ADR-103: a story carries no fingerprint of its own, so this is always 0 on
|
|
110
|
+
# the story tier and the re-point branch below never runs there. The branch
|
|
111
|
+
# is retained for any artefact that does carry one.
|
|
108
112
|
was_valid=0
|
|
109
|
-
if is_story_map_ratified "$f"; then was_valid=1; fi
|
|
113
|
+
if [ -n "$(oversight_stored_hash "$f")" ] && is_story_map_ratified "$f"; then was_valid=1; fi
|
|
110
114
|
|
|
111
115
|
# Drop the FIRST matching mirror line and nothing else — surrounding blank
|
|
112
116
|
# lines are left exactly as they are. The template puts the mirror in a run
|
|
@@ -132,7 +136,7 @@ for d in "${dirs[@]}"; do
|
|
|
132
136
|
refingerprinted=$((refingerprinted + 1))
|
|
133
137
|
printf 'MIGRATE %s — mirror removed, fingerprint re-pointed (ratification preserved)\n' "$f"
|
|
134
138
|
else
|
|
135
|
-
printf 'MIGRATE %s — mirror removed (
|
|
139
|
+
printf 'MIGRATE %s — mirror removed (no fingerprint of its own to re-point)\n' "$f"
|
|
136
140
|
fi
|
|
137
141
|
# STORY-*.md only. `README.md` documents the template and legitimately
|
|
138
142
|
# contains a `**Status**: <status>` example; scanning it would report a
|
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
# Story Rankings + Done tables, and reports each disagreement.
|
|
8
8
|
#
|
|
9
9
|
# Usage:
|
|
10
|
-
# reconcile-stories.sh [<stories-dir> [<problems-dir> [<rfcs-dir> [<jtbd-dir>]]]]
|
|
10
|
+
# reconcile-stories.sh [<stories-dir> [<problems-dir> [<rfcs-dir> [<jtbd-dir> [<story-maps-dir>]]]]]
|
|
11
11
|
#
|
|
12
12
|
# Defaults:
|
|
13
13
|
# <stories-dir> = ./docs/stories
|
|
14
14
|
# <problems-dir> = ./docs/problems (when supplied + on disk; reverse trace)
|
|
15
15
|
# <rfcs-dir> = ./docs/rfcs (when supplied + on disk; reverse trace)
|
|
16
16
|
# <jtbd-dir> = ./docs/jtbd (when supplied + on disk; reverse trace)
|
|
17
|
+
# <story-maps-dir> = ./docs/story-maps (row-backed RFC resolution)
|
|
17
18
|
#
|
|
18
19
|
# Exit codes:
|
|
19
20
|
# 0 = clean (README matches filesystem)
|
|
@@ -71,10 +72,12 @@
|
|
|
71
72
|
|
|
72
73
|
set -uo pipefail
|
|
73
74
|
|
|
75
|
+
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
74
76
|
STORIES_DIR="${1:-docs/stories}"
|
|
75
77
|
PROBLEMS_DIR="${2:-$(dirname "$STORIES_DIR")/problems}"
|
|
76
78
|
RFCS_DIR="${3:-$(dirname "$STORIES_DIR")/rfcs}"
|
|
77
79
|
JTBD_DIR="${4:-$(dirname "$STORIES_DIR")/jtbd}"
|
|
80
|
+
MAPS_DIR="${5:-$(dirname "$STORIES_DIR")/story-maps}"
|
|
78
81
|
README="${STORIES_DIR}/README.md"
|
|
79
82
|
|
|
80
83
|
# ── Pre-checks ──────────────────────────────────────────────────────────────
|
|
@@ -183,6 +186,8 @@ done
|
|
|
183
186
|
|
|
184
187
|
reverse_trace_pass() {
|
|
185
188
|
local parent_dir="$1" parent_kind="$2" parent_id_pattern="$3"
|
|
189
|
+
local -a matches=()
|
|
190
|
+
local pfile row_json
|
|
186
191
|
[ ! -d "$parent_dir" ] && return 0
|
|
187
192
|
|
|
188
193
|
shopt -s nullglob globstar
|
|
@@ -198,19 +203,35 @@ reverse_trace_pass() {
|
|
|
198
203
|
parent_claims=$(awk -v k="^${parent_kind}:" '$0 ~ k {gsub(/[][]/,""); gsub(/,/," "); for(i=2;i<=NF;i++)print $i; exit}' "$sf")
|
|
199
204
|
for pid in $parent_claims; do
|
|
200
205
|
# Resolve parent file under parent_dir
|
|
206
|
+
matches=()
|
|
201
207
|
case "$parent_kind" in
|
|
202
208
|
problems)
|
|
203
209
|
pnum="${pid#P}"
|
|
204
|
-
|
|
210
|
+
matches=("$parent_dir"/${pnum}-*.md "$parent_dir"/*/${pnum}-*.md)
|
|
205
211
|
;;
|
|
206
212
|
rfcs)
|
|
207
|
-
|
|
213
|
+
matches=("$parent_dir"/${pid}-*.md)
|
|
208
214
|
;;
|
|
209
215
|
jtbd)
|
|
210
|
-
|
|
216
|
+
matches=("$parent_dir"/*/${pid}-*.md)
|
|
211
217
|
;;
|
|
212
|
-
*)
|
|
218
|
+
*) matches=() ;;
|
|
213
219
|
esac
|
|
220
|
+
pfile="${matches[0]:-}"
|
|
221
|
+
|
|
222
|
+
# ADR-103: a release row may be the RFC, so no markdown parent exists.
|
|
223
|
+
# In that case the map row itself is the reverse trace and must contain
|
|
224
|
+
# this story. Query the canonical map island rather than scraping HTML.
|
|
225
|
+
if [ "$parent_kind" = rfcs ] && [ -z "$pfile" ]; then
|
|
226
|
+
row_json=$("$HERE/story-map-query.sh" find-rfc "$pid" --maps-dir "$MAPS_DIR" 2>/dev/null || true)
|
|
227
|
+
if [ -z "$row_json" ] || [ "$row_json" = "[]" ]; then
|
|
228
|
+
DRIFT_LINES+=("UNRESOLVED_RFC_TRACE ${sid} claims=${pid}")
|
|
229
|
+
elif ! grep -qF "\"${sid}\"" <<< "$row_json"; then
|
|
230
|
+
DRIFT_LINES+=("MISSING_REVERSE_TRACE ${sid} in ${pid} release row")
|
|
231
|
+
fi
|
|
232
|
+
continue
|
|
233
|
+
fi
|
|
234
|
+
|
|
214
235
|
[ -z "$pfile" ] && continue
|
|
215
236
|
|
|
216
237
|
# Check parent's ## Stories section contains this story's ID
|