@ngocsangairvds/vsaf 4.1.15 → 4.1.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ngocsangairvds/vsaf",
3
- "version": "4.1.15",
3
+ "version": "4.1.17",
4
4
  "description": "logging step",
5
5
  "main": "packages/core/dist/index.js",
6
6
  "types": "packages/core/dist/index.d.ts",
@@ -46,7 +46,7 @@ uv run --directory ~/.claude/vds-scripts --package audit_orchestrator \
46
46
  ## Targeted Rerun Commands
47
47
 
48
48
  > **Recommended for runs expected to take more than 5 minutes**: use `./scripts/run-audit-in-tmux.sh` to prevent session timeout orphaning. The parent process must stay alive while child workflow processes run — tmux ensures this. See [cli-commands.md](./cli-commands.md#persistent-execution) for the full pattern.
49
- > `run-audit-in-tmux.sh` inherits the shared-env bootstrap behavior of `worktree_uv.sh`, so normal runs should load `VDS_AUDIT_STATE_DSN` and other shared runtime keys from `~/.vds/.env` automatically. Explicit `--state-dsn` is still propagated to child workflow lanes after the parent resolves it.
49
+ > Normal runs load `VDS_AUDIT_STATE_DSN` and other shared runtime keys from `~/.vds/.env` automatically via `vds_cli_common.env`. Explicit `--state-dsn` is still propagated to child workflow lanes after the parent resolves it.
50
50
 
51
51
  Use the effective repo-cache root for your environment as defined by the portable Phase 2 cache-root model documented in `portable-paths-and-config.md`.
52
52
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  Current command surface is WHO-owned and runs from `WHO-project/vds-scripts`.
6
6
  When operating from a dedicated `vds-scripts` worktree, prefer `uv run --directory ~/.claude/vds-scripts ...` so a different checkout's active `.venv` does not leak into the current worktree.
7
- `worktree_uv.sh` also bootstraps missing shared runtime env from `~/.vds/.env` (or `VDS_ENV_FILE`) and re-roots execution to the owning repo, so detached shells and tmux sessions do not depend on manual `source ~/.vds/.env`.
7
+ The shared runtime env is bootstrapped from `~/.vds/.env` (or `VDS_ENV_FILE`) automatically by `vds_cli_common.env`, so detached shells and tmux sessions do not depend on manual `source ~/.vds/.env`.
8
8
 
9
9
  > **Interactive use vs. shell scripts**
10
10
  >
@@ -48,7 +48,7 @@ uv run --directory ~/.claude/vds-scripts --package audit_orchestrator \
48
48
  ## Targeted Rerun Commands
49
49
 
50
50
  > **Recommended for runs expected to take more than 5 minutes**: use `./scripts/run-audit-in-tmux.sh` to prevent session timeout orphaning. The parent process must stay alive while child workflow processes run — tmux ensures this. See [cli-commands.md](./cli-commands.md#persistent-execution) for the full pattern.
51
- > `run-audit-in-tmux.sh` inherits the shared-env bootstrap behavior of `worktree_uv.sh`, so normal runs should load `VDS_AUDIT_STATE_DSN` and other shared runtime keys from `~/.vds/.env` automatically. Explicit `--state-dsn` is still propagated to child workflow lanes after the parent resolves it.
51
+ > Normal runs load `VDS_AUDIT_STATE_DSN` and other shared runtime keys from `~/.vds/.env` automatically via `vds_cli_common.env`. Explicit `--state-dsn` is still propagated to child workflow lanes after the parent resolves it.
52
52
 
53
53
  Use the effective repo-cache root for your environment as defined by the portable Phase 2 cache-root model documented in `portable-paths-and-config.md`.
54
54
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  Current command surface is WHO-owned and runs from `WHO-project/vds-scripts`.
8
8
  When operating from a dedicated `vds-scripts` worktree, prefer `uv run --directory ~/.claude/vds-scripts ...` so a different checkout's active `.venv` does not leak into the current worktree.
9
- `worktree_uv.sh` also bootstraps missing shared runtime env from `~/.vds/.env` (or `VDS_ENV_FILE`) and re-roots execution to the owning repo, so detached shells and tmux sessions do not depend on manual `source ~/.vds/.env`.
9
+ The shared runtime env is bootstrapped from `~/.vds/.env` (or `VDS_ENV_FILE`) automatically by `vds_cli_common.env`, so detached shells and tmux sessions do not depend on manual `source ~/.vds/.env`.
10
10
 
11
11
  > **Interactive use vs. shell scripts**
12
12
  >
@@ -165,7 +165,7 @@ def test_render_installation_guide(sample_config: ConfluenceHubConfig) -> None:
165
165
 
166
166
  assert "<h1>Ecosystem Installation Guide</h1>" in body
167
167
  assert "git clone" in body
168
- assert "worktree_uv.sh sync" in body
168
+ assert "uv sync --directory ~/.claude/vds-scripts --all-packages" in body
169
169
 
170
170
 
171
171
  def test_render_quick_reference_vds_scripts_fallback(sample_config: ConfluenceHubConfig) -> None:
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import os
4
- import subprocess
5
4
  from pathlib import Path
6
5
 
7
6
  import pytest
@@ -19,10 +18,6 @@ def _write_env(path: Path, content: str) -> None:
19
18
  path.write_text(content, encoding="utf-8")
20
19
 
21
20
 
22
- def _worktree_uv_script() -> Path:
23
- return Path(__file__).resolve().parents[2] / "scripts" / "worktree_uv.sh"
24
-
25
-
26
21
  def test_load_shared_env_first_file_wins_when_keys_overlap(tmp_path: Path, monkeypatch) -> None:
27
22
  primary = tmp_path / "primary.env"
28
23
  fallback = tmp_path / "fallback.env"
@@ -133,103 +128,3 @@ def test_render_shared_env_exports_override_true_emits_existing_values(tmp_path:
133
128
  assert exports == "export TOKEN=from-file"
134
129
 
135
130
 
136
- @pytest.mark.integration
137
- @pytest.mark.slow
138
- def test_worktree_uv_bootstraps_shared_env_for_python_entrypoint(tmp_path: Path) -> None:
139
- env_file = tmp_path / "shared.env"
140
- _write_env(env_file, "VDS_AUDIT_STATE_DSN=postgresql://from-file/state\n")
141
-
142
- script = _worktree_uv_script()
143
- result = subprocess.run(
144
- [
145
- str(script),
146
- "run",
147
- "--project",
148
- "audit_orchestrator",
149
- "python",
150
- "-c",
151
- "import os; print(os.getenv('VDS_AUDIT_STATE_DSN') or '')",
152
- ],
153
- cwd=script.parent.parent,
154
- env={
155
- "HOME": os.environ["HOME"],
156
- "PATH": os.environ["PATH"],
157
- "VDS_ENV_FILE": str(env_file),
158
- },
159
- capture_output=True,
160
- text=True,
161
- check=False,
162
- timeout=30,
163
- )
164
-
165
- assert result.returncode == 0, result.stderr
166
- assert result.stdout.strip() == "postgresql://from-file/state"
167
-
168
-
169
- @pytest.mark.integration
170
- @pytest.mark.slow
171
- def test_worktree_uv_preserves_explicit_state_dsn_over_shared_env(tmp_path: Path) -> None:
172
- env_file = tmp_path / "shared.env"
173
- _write_env(env_file, "VDS_AUDIT_STATE_DSN=postgresql://from-file/state\n")
174
-
175
- script = _worktree_uv_script()
176
- result = subprocess.run(
177
- [
178
- str(script),
179
- "run",
180
- "--project",
181
- "audit_orchestrator",
182
- "python",
183
- "-c",
184
- "import os; print(os.getenv('VDS_AUDIT_STATE_DSN') or '')",
185
- ],
186
- cwd=script.parent.parent,
187
- env={
188
- "HOME": os.environ["HOME"],
189
- "PATH": os.environ["PATH"],
190
- "VDS_ENV_FILE": str(env_file),
191
- "VDS_AUDIT_STATE_DSN": "postgresql://from-shell/state",
192
- },
193
- capture_output=True,
194
- text=True,
195
- check=False,
196
- timeout=30,
197
- )
198
-
199
- assert result.returncode == 0, result.stderr
200
- assert result.stdout.strip() == "postgresql://from-shell/state"
201
-
202
-
203
- @pytest.mark.integration
204
- @pytest.mark.slow
205
- def test_worktree_uv_bootstraps_shared_env_from_external_cwd(tmp_path: Path) -> None:
206
- env_file = tmp_path / "shared.env"
207
- _write_env(env_file, "VDS_AUDIT_STATE_DSN=postgresql://from-file/state\n")
208
-
209
- script = _worktree_uv_script()
210
- outside_cwd = tmp_path / "outside"
211
- outside_cwd.mkdir()
212
- result = subprocess.run(
213
- [
214
- str(script),
215
- "run",
216
- "--project",
217
- "audit_orchestrator",
218
- "python",
219
- "-c",
220
- "import os; print(os.getenv('VDS_AUDIT_STATE_DSN') or '')",
221
- ],
222
- cwd=outside_cwd,
223
- env={
224
- "HOME": os.environ["HOME"],
225
- "PATH": os.environ["PATH"],
226
- "VDS_ENV_FILE": str(env_file),
227
- },
228
- capture_output=True,
229
- text=True,
230
- check=False,
231
- timeout=30,
232
- )
233
-
234
- assert result.returncode == 0, result.stderr
235
- assert result.stdout.strip() == "postgresql://from-file/state"
@@ -112,6 +112,20 @@ uv run --directory ~/.claude/vds-scripts --package spec_orchestrator vds-spec --
112
112
  - Research-backed investigation → `research-skill`
113
113
  - Structural graph review → `code-review-graph-skill`
114
114
 
115
+ ## Mandatory Rules
116
+
117
+ ### Confluence Content Export
118
+
119
+ When fetching or exporting Confluence page content (especially BRD, KBNV, SRS pages with tables and images), you **MUST** read and follow `references/confluence-content-export.md` before proceeding. Key rules:
120
+
121
+ 1. **Use `body.storage`** (not `body.view`) as source of truth for image references
122
+ 2. **Only download attachments referenced in `body.storage`** — never bulk-download all attachments
123
+ 3. **Preserve image position** in output — replace `ac:image` with `![](images/...)` in-place, never collect at end
124
+ 4. **Keep HTML tables** in markdown output — do not flatten to pipe tables
125
+ 5. **Verify image count** matches between `body.storage` and output before reporting done
126
+
127
+ Failure to follow these rules causes silent data loss (wrong images downloaded, table structure destroyed, image↔text mapping broken).
128
+
115
129
  ## Progressive Disclosure
116
130
 
117
131
  Use these bundled references for depth:
@@ -121,6 +135,7 @@ Use these bundled references for depth:
121
135
  - `references/validation-commands.md`
122
136
  - `references/development-commands.md`
123
137
  - `references/specialist-routing.md`
138
+ - `references/confluence-content-export.md`
124
139
 
125
140
  ## Notes
126
141
 
@@ -0,0 +1,127 @@
1
+ # Confluence Content Export — Mandatory Rules
2
+
3
+ When extracting content from a Confluence page (BRD, KBNV, SRS, or any page with tables/images), follow these rules strictly. Violations cause data loss that is invisible to the user.
4
+
5
+ ## Rule 1 — Use `body.storage` as the Single Source of Truth for Image Refs
6
+
7
+ - **Never** rely on `body.view` for image references — it is post-render HTML that may add or drop images via macros.
8
+ - `body.storage` is raw XHTML containing `<ac:image><ri:attachment ri:filename="..."/></ac:image>` — this is the definitive set of inline images.
9
+ - **Only download attachments whose filename appears in `body.storage`**. Do not bulk-download from `attachments --list`.
10
+
11
+ ```bash
12
+ # Step 1: fetch storage body
13
+ uv run --directory ~/.claude/vds-scripts --package vds-cli \
14
+ vds-cli confluence content page <PAGE_ID> --expand body.storage
15
+ ```
16
+
17
+ ```python
18
+ # Step 2: extract referenced filenames from storage XHTML
19
+ import re
20
+ storage_xml: str = ... # body.storage content
21
+ referenced = set(re.findall(r'ri:filename="([^"]+)"', storage_xml))
22
+ # referenced = {'screen-login.png', 'flow-diagram.png', 'table-header.png'}
23
+ ```
24
+
25
+ ## Rule 2 — Preserve Image-to-Text Positional Mapping
26
+
27
+ When converting to markdown/local file:
28
+
29
+ - Replace each `<ac:image>...<ri:attachment ri:filename="X"/>...</ac:image>` with `![X](images/X)` **at its original position** in the document. Never collect images into a list at the end.
30
+ - If the content contains `<table>`, **keep HTML table markup** in the markdown output. Markdown viewers (GitHub, Obsidian, VS Code) all render HTML tables. Do not flatten tables with nested bullets or merged cells into pure-markdown `|` tables — this destroys row/column relationships.
31
+ - Place the `images/` folder at the same level as `content.md`.
32
+
33
+ ```python
34
+ # Step 3: convert storage XHTML → markdown with inline image refs
35
+ import re
36
+
37
+ def storage_to_markdown(storage_xml: str) -> str:
38
+ """Convert Confluence storage XHTML to markdown, preserving image positions and tables."""
39
+ result = storage_xml
40
+
41
+ # Replace ac:image with markdown image refs (in-place)
42
+ def replace_ac_image(match: re.Match) -> str:
43
+ filename = re.search(r'ri:filename="([^"]+)"', match.group(0))
44
+ if filename:
45
+ name = filename.group(1)
46
+ return f'![{name}](images/{name})'
47
+ return match.group(0)
48
+
49
+ result = re.sub(
50
+ r'<ac:image[^>]*>.*?</ac:image>',
51
+ replace_ac_image,
52
+ result,
53
+ flags=re.DOTALL,
54
+ )
55
+
56
+ # Keep <table>...</table> blocks as-is (HTML in markdown)
57
+ # Convert other XHTML elements to markdown as usual:
58
+ # <p> → newline, <h1> → #, <li> → -, <strong> → **, etc.
59
+ # ...
60
+
61
+ return result
62
+ ```
63
+
64
+ ## Rule 3 — Classify Downloaded Attachments
65
+
66
+ If the user requests "all attachments" (audit/backup scenario):
67
+
68
+ - Split into two directories:
69
+ - `images/` — files referenced inline in `body.storage`
70
+ - `images_unused/` — attached to the page but **not** referenced inline
71
+ - Report summary: `Used: N inline / Total: M attached`
72
+
73
+ ```bash
74
+ # Download only referenced attachments
75
+ for filename in $REFERENCED_FILES; do
76
+ att_id=$(vds-cli confluence content list-attachments <PAGE_ID> --json-only \
77
+ | jq -r ".[] | select(.title == \"$filename\") | .id")
78
+ uv run --directory ~/.claude/vds-scripts --package vds-cli \
79
+ vds-cli confluence content download-attachment "$att_id" --out "images/$filename"
80
+ done
81
+ ```
82
+
83
+ ## Rule 4 — Verify Before Reporting "Done"
84
+
85
+ After generating `content.md`:
86
+
87
+ 1. Count inline image refs in output: `grep -c '!\[.*\](images/.*)' content.md`
88
+ 2. Count `ri:filename` occurrences in `body.storage`
89
+ 3. **Both counts must match.** If they differ, log a warning with the diff:
90
+
91
+ ```
92
+ WARNING: Image ref mismatch — storage has 3 ri:filename refs, content.md has 2 inline images.
93
+ Missing: screen-step3.png
94
+ ```
95
+
96
+ Do not report success if counts diverge.
97
+
98
+ ## Rule 5 — Correct Invocation Sequence (Step by Step)
99
+
100
+ ```bash
101
+ # B1: Fetch storage body (source of truth for image refs)
102
+ uv run --directory ~/.claude/vds-scripts --package vds-cli \
103
+ vds-cli confluence content page <PAGE_ID> --expand body.storage
104
+
105
+ # B2: Extract ri:filename values from storage XML (see Rule 1 Python snippet)
106
+
107
+ # B3: Download only referenced attachments
108
+ uv run --directory ~/.claude/vds-scripts --package vds-cli \
109
+ vds-cli confluence content download-attachment <ATT_ID> --out images/<filename>
110
+
111
+ # B4: Convert storage XHTML → markdown (see Rule 2 Python snippet)
112
+ # - ac:image → ![](images/...) in-place
113
+ # - tables stay as HTML
114
+ # - output: content.md + images/
115
+
116
+ # B5: Verify (see Rule 4)
117
+ ```
118
+
119
+ ## Anti-Patterns (What NOT to Do)
120
+
121
+ | Anti-pattern | Why it fails |
122
+ |---|---|
123
+ | Fetch `body.view` then strip HTML | Loses image positions, macro-injected images leak in |
124
+ | `attachments --list` then download all | Downloads orphaned/versioned files (59 vs 3 actually used) |
125
+ | Flatten `<table>` to markdown `\|` pipe tables | Destroys merged cells, nested bullets, colspan/rowspan |
126
+ | Collect images at end of document | Breaks image↔context mapping (which screen? which step?) |
127
+ | Skip verification | Silent data loss — user trusts output without knowing images are missing |