docs: plan generated wiki stale purge
This commit is contained in:
@@ -0,0 +1,812 @@
|
||||
# Generated Wiki Stale Purge Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add an opt-in `purge` stale policy that permanently removes only manifest-tracked generated NodeBB wiki topics that no longer exist in current generator output.
|
||||
|
||||
**Architecture:** Keep stale ownership and candidate selection inside the existing `deploy-wiki` manifest planner. Extend the deploy result/plan model with purge counts and purge actions, add a thin NodeBB topic purge client call, remove successfully purged entries from the next manifest, and surface the policy through existing toolkit validation and module release/docs paths.
|
||||
|
||||
**Tech Stack:** Go toolkit code and `httptest` deploy tests, YAML-backed toolkit project config validation, POSIX shell module release wrapper, Markdown docs.
|
||||
|
||||
---
|
||||
|
||||
## File Map
|
||||
|
||||
Toolkit files:
|
||||
|
||||
- Modify `internal/project/project.go`: validate `purge` as a supported stale policy and remove the unsupported `unpublish` config assumption.
|
||||
- Modify `internal/project/project_test.go`: prove project config accepts `purge` and rejects unsupported stale policies.
|
||||
- Modify `internal/topdata/wiki_deploy.go`: add purge deploy state, stale purge planning, manifest removal, and NodeBB topic purge requests.
|
||||
- Modify `internal/topdata/wiki_deploy_test.go`: cover dry-run/live purge and missing-topic-id refusal without changing archive/report behavior.
|
||||
- Modify `internal/app/app.go`: expose `purge` in deploy CLI help and print `purged` deploy counts.
|
||||
- Modify `internal/app/app_test.go`: cover CLI output/help parsing for the new count and policy text.
|
||||
- Modify `README.md`: document the destructive generated-manifest-scoped purge mode in the toolkit CLI docs.
|
||||
|
||||
Module files:
|
||||
|
||||
- Modify `../module/scripts/release-all.sh`: allow `SOW_MODULE_WIKI_DEPLOY_STALE_POLICY=purge`.
|
||||
- Modify `../module/README.md`: document release usage and the destructive boundary.
|
||||
- Modify `../module/topdata/README.md`: document `deploy-wiki --stale-policy purge`.
|
||||
- Modify `../module/tests/release-all-cache-cleanup.sh`: prove release automation forwards `purge` into both wiki deploy phases.
|
||||
|
||||
## Task 1: Align Stale Policy Validation
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/project/project.go`
|
||||
- Modify: `internal/project/project_test.go`
|
||||
|
||||
- [ ] **Step 1: Write the failing project-config test**
|
||||
|
||||
Add a project validation case in `internal/project/project_test.go` that loads stale policy config with `purge` and expects validation to pass:
|
||||
|
||||
```go
|
||||
func TestProjectValidationAcceptsWikiStalePurgePolicy(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeProjectFile(t, filepath.Join(root, ConfigFile), `
|
||||
module:
|
||||
name: Test Module
|
||||
resref: testmod
|
||||
topdata:
|
||||
wiki:
|
||||
stale_pages:
|
||||
default: report
|
||||
live_cleanup: purge
|
||||
`)
|
||||
|
||||
proj, err := Load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load project: %v", err)
|
||||
}
|
||||
if err := proj.ValidateLayout(); err != nil {
|
||||
t.Fatalf("validate purge stale policy: %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/project -run TestProjectValidationAcceptsWikiStalePurgePolicy -count=1
|
||||
```
|
||||
|
||||
Expected: FAIL because `topdata.wiki.stale_pages.live_cleanup "purge" is not supported`.
|
||||
|
||||
- [ ] **Step 3: Replace unsupported project validation vocabulary**
|
||||
|
||||
In `internal/project/project.go`, update the stale-policy switch from:
|
||||
|
||||
```go
|
||||
switch policy {
|
||||
case "report", "archive", "unpublish":
|
||||
default:
|
||||
failures = append(failures, fmt.Errorf("%s %q is not supported", key, policy))
|
||||
}
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```go
|
||||
switch policy {
|
||||
case "report", "archive", "purge":
|
||||
default:
|
||||
failures = append(failures, fmt.Errorf("%s %q is not supported", key, policy))
|
||||
}
|
||||
```
|
||||
|
||||
This removes the config-only `unpublish` value that the deploy command cannot execute.
|
||||
|
||||
- [ ] **Step 4: Add the unsupported-policy regression**
|
||||
|
||||
Add or extend a project validation test so `unpublish` and an arbitrary bad value fail:
|
||||
|
||||
```go
|
||||
func TestProjectValidationRejectsUnsupportedWikiStalePolicies(t *testing.T) {
|
||||
for _, policy := range []string{"unpublish", "remove-everything"} {
|
||||
t.Run(policy, func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeProjectFile(t, filepath.Join(root, ConfigFile), fmt.Sprintf(`
|
||||
module:
|
||||
name: Test Module
|
||||
resref: testmod
|
||||
topdata:
|
||||
wiki:
|
||||
stale_pages:
|
||||
live_cleanup: %s
|
||||
`, policy))
|
||||
|
||||
proj, err := Load(root)
|
||||
if err != nil {
|
||||
t.Fatalf("load project: %v", err)
|
||||
}
|
||||
err = proj.ValidateLayout()
|
||||
if err == nil || !strings.Contains(err.Error(), policy) {
|
||||
t.Fatalf("expected %q stale policy validation failure, got %v", policy, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Add `fmt` to the existing `internal/project/project_test.go` imports for the `Sprintf` YAML fixture.
|
||||
|
||||
- [ ] **Step 5: Run project tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/project -count=1
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Commit the validation change**
|
||||
|
||||
```bash
|
||||
git add internal/project/project.go internal/project/project_test.go
|
||||
git commit -m "feat: validate wiki stale purge policy"
|
||||
```
|
||||
|
||||
## Task 2: Plan Manifest-Scoped Purge Actions
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/topdata/wiki_deploy.go`
|
||||
- Modify: `internal/topdata/wiki_deploy_test.go`
|
||||
|
||||
- [ ] **Step 1: Write failing deploy tests for dry-run purge planning**
|
||||
|
||||
In `internal/topdata/wiki_deploy_test.go`, add:
|
||||
|
||||
```go
|
||||
func TestDeployWikiDryRunPlansTrackedStalePagePurge(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "skills"), 0755); err != nil {
|
||||
t.Fatalf("create source dir: %v", err)
|
||||
}
|
||||
manifestPath := filepath.Join(root, "deploy-manifest.json")
|
||||
if err := saveDeployManifest(manifestPath, wikiDeployManifest{
|
||||
Version: "nodebb-v1",
|
||||
Pages: map[string]wikiDeployManifestPage{
|
||||
"skills:retired": {Hash: "old-hash", Title: "Retired Skill", Namespace: "skills", TID: 7, PID: 42, CID: 3},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("write deploy manifest: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("dry-run purge must not call NodeBB, got %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result, err := DeployWikiWithOptions(&project.Project{}, DeployWikiOptions{
|
||||
SourceDir: sourceDir,
|
||||
Endpoint: server.URL,
|
||||
Token: "nodebb-token",
|
||||
ManifestPath: manifestPath,
|
||||
DryRun: true,
|
||||
StalePolicy: "purge",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("DeployWikiWithOptions stale purge dry run failed: %v", err)
|
||||
}
|
||||
if result.Stale != 1 || result.Purged != 1 {
|
||||
t.Fatalf("expected one planned stale purge, got %#v", result)
|
||||
}
|
||||
if _, ok := loadDeployManifest(manifestPath).Pages["skills:retired"]; !ok {
|
||||
t.Fatalf("dry-run purge must not change the deploy manifest")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write the failing missing-topic-id test**
|
||||
|
||||
Add:
|
||||
|
||||
```go
|
||||
func TestDeployWikiPurgeRefusesStaleManifestEntryWithoutTopicID(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "skills"), 0755); err != nil {
|
||||
t.Fatalf("create source dir: %v", err)
|
||||
}
|
||||
manifestPath := filepath.Join(root, "deploy-manifest.json")
|
||||
if err := saveDeployManifest(manifestPath, wikiDeployManifest{
|
||||
Version: "nodebb-v1",
|
||||
Pages: map[string]wikiDeployManifestPage{
|
||||
"skills:retired": {Hash: "old-hash", Title: "Retired Skill", Namespace: "skills", PID: 42, CID: 3},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("write deploy manifest: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("missing-topic-id purge must not call NodeBB, got %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
_, err := DeployWikiWithOptions(&project.Project{}, DeployWikiOptions{
|
||||
SourceDir: sourceDir,
|
||||
Endpoint: server.URL,
|
||||
Token: "nodebb-token",
|
||||
ManifestPath: manifestPath,
|
||||
DryRun: true,
|
||||
StalePolicy: "purge",
|
||||
}, nil)
|
||||
if err == nil || !strings.Contains(err.Error(), "topic id") {
|
||||
t.Fatalf("expected stale purge to require tracked topic id, got %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run the focused tests to verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/topdata -run 'TestDeployWiki.*Purge' -count=1
|
||||
```
|
||||
|
||||
Expected: FAIL because `purge` is not yet accepted and `deployResult` has no `Purged` field.
|
||||
|
||||
- [ ] **Step 4: Add purge plan/result model fields**
|
||||
|
||||
In `internal/topdata/wiki_deploy.go`, extend `deployResult`:
|
||||
|
||||
```go
|
||||
type deployResult struct {
|
||||
LocalPages int
|
||||
Created int
|
||||
Updated int
|
||||
Stale int
|
||||
Archived int
|
||||
Purged int
|
||||
Skipped int
|
||||
Drifted int
|
||||
Renamed int
|
||||
Manifest string
|
||||
}
|
||||
```
|
||||
|
||||
Allow `purge` during deploy option validation:
|
||||
|
||||
```go
|
||||
if opts.StalePolicy != "" &&
|
||||
opts.StalePolicy != "report" &&
|
||||
opts.StalePolicy != "archive" &&
|
||||
opts.StalePolicy != "purge" {
|
||||
return deployResult{}, fmt.Errorf("wiki stale policy %q is not supported", opts.StalePolicy)
|
||||
}
|
||||
```
|
||||
|
||||
Add the purge count to the deploy plan progress line so dry-run logs show the destructive action explicitly:
|
||||
|
||||
```go
|
||||
progress(fmt.Sprintf("NodeBB wiki plan: create %d, update %d, rename %d, skip %d, stale %d, archive %d, purge %d, drift %d",
|
||||
result.Created,
|
||||
result.Updated,
|
||||
result.Renamed,
|
||||
result.Skipped,
|
||||
result.Stale,
|
||||
result.Archived,
|
||||
result.Purged,
|
||||
result.Drifted,
|
||||
))
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Add stale purge planning**
|
||||
|
||||
In the stale manifest loop inside `planNodeBBDeploy`, keep the current report/archive branches and add the purge branch:
|
||||
|
||||
```go
|
||||
case "purge":
|
||||
if entry.TID == 0 {
|
||||
return nil, result, next, fmt.Errorf("stale generated wiki page %q cannot be purged without tracked NodeBB topic id", pageID)
|
||||
}
|
||||
result.Purged++
|
||||
delete(next.Pages, pageID)
|
||||
plans = append(plans, wikiDeployPlan{
|
||||
Page: wikiDeployPage{PageID: pageID, Title: entry.Title, Namespace: entry.Namespace},
|
||||
Entry: entry,
|
||||
Action: "purge",
|
||||
})
|
||||
```
|
||||
|
||||
Use a `switch policy` or equivalent structure so `report`, `archive`, and `purge` remain explicit. Do not add namespace search or title matching to find purge targets.
|
||||
|
||||
- [ ] **Step 6: Run the focused purge planning tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/topdata -run 'TestDeployWiki.*Purge' -count=1
|
||||
```
|
||||
|
||||
Expected: the dry-run and missing-topic-id purge tests pass once they compile.
|
||||
|
||||
- [ ] **Step 7: Commit purge planning**
|
||||
|
||||
```bash
|
||||
git add internal/topdata/wiki_deploy.go internal/topdata/wiki_deploy_test.go
|
||||
git commit -m "feat: plan stale generated wiki purges"
|
||||
```
|
||||
|
||||
## Task 3: Execute NodeBB Topic Purges
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/topdata/wiki_deploy.go`
|
||||
- Modify: `internal/topdata/wiki_deploy_test.go`
|
||||
|
||||
- [ ] **Step 1: Write the failing live purge test**
|
||||
|
||||
Add a live stale purge test in `internal/topdata/wiki_deploy_test.go`:
|
||||
|
||||
```go
|
||||
func TestDeployWikiPurgesTrackedStaleGeneratedTopic(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "skills"), 0755); err != nil {
|
||||
t.Fatalf("create source dir: %v", err)
|
||||
}
|
||||
manifestPath := filepath.Join(root, "deploy-manifest.json")
|
||||
if err := saveDeployManifest(manifestPath, wikiDeployManifest{
|
||||
Version: "nodebb-v1",
|
||||
Pages: map[string]wikiDeployManifestPage{
|
||||
"skills:retired": {Hash: "old-hash", Title: "Retired Skill", Namespace: "skills", TID: 7, PID: 42, CID: 3},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("write deploy manifest: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete || r.URL.Path != "/api/v3/topics/7" {
|
||||
t.Fatalf("unexpected request %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"status": map[string]any{"code": "ok"}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result, err := DeployWikiWithOptions(&project.Project{}, DeployWikiOptions{
|
||||
SourceDir: sourceDir,
|
||||
Endpoint: server.URL,
|
||||
Token: "nodebb-token",
|
||||
ManifestPath: manifestPath,
|
||||
StalePolicy: "purge",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("DeployWikiWithOptions stale purge failed: %v", err)
|
||||
}
|
||||
if result.Stale != 1 || result.Purged != 1 {
|
||||
t.Fatalf("expected one stale purge, got %#v", result)
|
||||
}
|
||||
if _, ok := loadDeployManifest(manifestPath).Pages["skills:retired"]; ok {
|
||||
t.Fatalf("expected purged stale manifest entry to be removed")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This route is the NodeBB core purge route. `DELETE /api/v3/topics/:tid/state` is the separate soft-delete route and must not be used here.
|
||||
|
||||
- [ ] **Step 2: Run the live purge test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/topdata -run 'TestDeployWiki.*Live.*Purge|TestDeployWiki.*Purges.*Stale' -count=1
|
||||
```
|
||||
|
||||
Expected: FAIL because purge plans are not yet executed by the NodeBB client.
|
||||
|
||||
- [ ] **Step 3: Add the NodeBB purge client method**
|
||||
|
||||
Place a thin method near `updatePost` and `renameWikiPage` in `internal/topdata/wiki_deploy.go`:
|
||||
|
||||
```go
|
||||
func (c *nodeBBClient) purgeTopic(tid int) error {
|
||||
if tid == 0 {
|
||||
return fmt.Errorf("NodeBB topic purge requires topic id")
|
||||
}
|
||||
return c.request(http.MethodDelete, fmt.Sprintf("/api/v3/topics/%d", tid), nil, nil)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Execute purge plans**
|
||||
|
||||
In the live plan execution switch, add:
|
||||
|
||||
```go
|
||||
case "purge":
|
||||
if err := client.purgeTopic(plan.Entry.TID); err != nil {
|
||||
return result, fmt.Errorf("deploy wiki page %q: purge NodeBB topic %d: %w", plan.Page.PageID, plan.Entry.TID, err)
|
||||
}
|
||||
```
|
||||
|
||||
Do not acquire wiki edit locks for purge: the NodeBB topic purge endpoint owns its own privilege checks.
|
||||
|
||||
- [ ] **Step 5: Keep current generated pages out of purge coverage**
|
||||
|
||||
Add this test so a current generated page with the same manifest page id is skipped instead of purged:
|
||||
|
||||
```go
|
||||
func TestDeployWikiPurgeDoesNotTargetCurrentGeneratedPages(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
sourceDir := filepath.Join(root, "pages")
|
||||
if err := os.MkdirAll(filepath.Join(sourceDir, "skills"), 0755); err != nil {
|
||||
t.Fatalf("create source dir: %v", err)
|
||||
}
|
||||
generated := "[//]: # (sow-topdata-wiki:managed:start)\n# Athletics\n\nGenerated athletics page\n[//]: # (sow-topdata-wiki:managed:end)\n"
|
||||
if err := os.WriteFile(filepath.Join(sourceDir, "skills", "athletics.md"), []byte(generated), 0644); err != nil {
|
||||
t.Fatalf("write source page: %v", err)
|
||||
}
|
||||
manifestPath := filepath.Join(root, "deploy-manifest.json")
|
||||
if err := saveDeployManifest(manifestPath, wikiDeployManifest{
|
||||
Version: "nodebb-v1",
|
||||
Pages: map[string]wikiDeployManifestPage{
|
||||
"skills:athletics": {
|
||||
Hash: computeManagedHash(generated),
|
||||
Title: "Athletics",
|
||||
Namespace: "skills",
|
||||
TID: 7,
|
||||
PID: 42,
|
||||
CID: 3,
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("write deploy manifest: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("current generated page must not call NodeBB during matching-hash deploy, got %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
result, err := DeployWikiWithOptions(&project.Project{}, DeployWikiOptions{
|
||||
SourceDir: sourceDir,
|
||||
Endpoint: server.URL,
|
||||
Token: "nodebb-token",
|
||||
ManifestPath: manifestPath,
|
||||
StalePolicy: "purge",
|
||||
}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("DeployWikiWithOptions current page purge deploy failed: %v", err)
|
||||
}
|
||||
if result.Stale != 0 || result.Purged != 0 || result.Skipped != 1 {
|
||||
t.Fatalf("expected current generated page to skip purge, got %#v", result)
|
||||
}
|
||||
if _, ok := loadDeployManifest(manifestPath).Pages["skills:athletics"]; !ok {
|
||||
t.Fatalf("expected current generated page to remain in deploy manifest")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Run deploy tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/topdata -run 'TestDeployWiki' -count=1
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit purge execution**
|
||||
|
||||
```bash
|
||||
git add internal/topdata/wiki_deploy.go internal/topdata/wiki_deploy_test.go
|
||||
git commit -m "feat: purge stale generated wiki topics"
|
||||
```
|
||||
|
||||
## Task 4: Surface Purge In Toolkit CLI And Docs
|
||||
|
||||
**Files:**
|
||||
- Modify: `internal/app/app.go`
|
||||
- Modify: `internal/app/app_test.go`
|
||||
- Modify: `README.md`
|
||||
|
||||
- [ ] **Step 1: Write failing CLI-output coverage**
|
||||
|
||||
In `internal/app/app_test.go`, extend `TestTopdataConsoleDebugProgressAndRelativePaths` so the sample progress and emit call include a purge count:
|
||||
|
||||
```go
|
||||
console.progress("NodeBB wiki plan: create 1, update 2, skip 3, stale 4, archive 5, purge 6, drift 0")
|
||||
console.emitWikiDeployResult(10, 1, 2, 3, 4, 5, 6, 0, "/workspace/project/build/wiki/deploy-manifest.json")
|
||||
```
|
||||
|
||||
Assert:
|
||||
|
||||
```go
|
||||
if !strings.Contains(output, "purged: 6") {
|
||||
t.Fatalf("expected purged deploy count, got %q", output)
|
||||
}
|
||||
```
|
||||
|
||||
Update the debug progress expectation to include `purge 6`.
|
||||
|
||||
- [ ] **Step 2: Run focused app tests to verify failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/app -run TestTopdataConsoleDebugProgressAndRelativePaths -count=1
|
||||
```
|
||||
|
||||
Expected: FAIL because `emitWikiDeployResult` does not yet take a purge count.
|
||||
|
||||
- [ ] **Step 3: Print purge counts**
|
||||
|
||||
Update the emit signature and output in `internal/app/app.go`:
|
||||
|
||||
```go
|
||||
func (c *topdataConsole) emitWikiDeployResult(localPages, created, updated, skipped, stale, archived, purged, drifted int, manifest string) {
|
||||
spin.linebreak()
|
||||
fmt.Fprintln(c.stdout, "Deploy Wiki ----------")
|
||||
fmt.Fprintf(c.stdout, "project: %s\n", c.projectName)
|
||||
fmt.Fprintf(c.stdout, "local pages: %d\n", localPages)
|
||||
fmt.Fprintf(c.stdout, "created: %d\n", created)
|
||||
fmt.Fprintf(c.stdout, "updated: %d\n", updated)
|
||||
fmt.Fprintf(c.stdout, "skipped: %d\n", skipped)
|
||||
fmt.Fprintf(c.stdout, "stale: %d\n", stale)
|
||||
fmt.Fprintf(c.stdout, "archived: %d\n", archived)
|
||||
fmt.Fprintf(c.stdout, "purged: %d\n", purged)
|
||||
fmt.Fprintf(c.stdout, "drifted: %d\n", drifted)
|
||||
fmt.Fprintf(c.stdout, "manifest: %s\n", c.relPath(manifest))
|
||||
}
|
||||
```
|
||||
|
||||
Pass `result.Purged` from the deploy command call site.
|
||||
|
||||
- [ ] **Step 4: Update CLI usage text**
|
||||
|
||||
Change deploy-wiki help text in `internal/app/app.go` from:
|
||||
|
||||
```go
|
||||
[--stale-policy <report|archive>]
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```go
|
||||
[--stale-policy <report|archive|purge>]
|
||||
```
|
||||
|
||||
Add this focused help assertion in `internal/app/app_test.go`:
|
||||
|
||||
```go
|
||||
func TestParseDeployWikiHelpListsPurgeStalePolicy(t *testing.T) {
|
||||
_, err := parseDeployWikiArgs("deploy-wiki", []string{"--help"})
|
||||
if err == nil || !strings.Contains(err.Error(), "--stale-policy <report|archive|purge>") {
|
||||
t.Fatalf("expected deploy-wiki help to list purge stale policy, got %v", err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Update toolkit README**
|
||||
|
||||
In `README.md`, update the `deploy-wiki` usage and stale-page explanation so it states:
|
||||
|
||||
```markdown
|
||||
`--stale-policy purge` permanently removes only stale generated topics already
|
||||
tracked in the wiki deploy manifest. Dry-run that policy before the live run;
|
||||
it never discovers manual wiki pages from NodeBB for purge.
|
||||
```
|
||||
|
||||
Keep `report` as the documented default and `archive` as the nondestructive cleanup option.
|
||||
|
||||
- [ ] **Step 6: Run app tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
go test ./internal/app -count=1
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit toolkit CLI/docs**
|
||||
|
||||
```bash
|
||||
git add internal/app/app.go internal/app/app_test.go README.md
|
||||
git commit -m "docs: expose wiki stale purge policy"
|
||||
```
|
||||
|
||||
## Task 5: Expose Purge Through Module Release Workflow
|
||||
|
||||
**Files:**
|
||||
- Modify: `../module/scripts/release-all.sh`
|
||||
- Modify: `../module/README.md`
|
||||
- Modify: `../module/topdata/README.md`
|
||||
- Modify: `../module/tests/release-all-cache-cleanup.sh`
|
||||
|
||||
- [ ] **Step 1: Write failing release wrapper coverage**
|
||||
|
||||
In `../module/tests/release-all-cache-cleanup.sh`, set the stale policy in the existing release invocation:
|
||||
|
||||
```bash
|
||||
SOW_MODULE_WIKI_DEPLOY_STALE_POLICY="purge" \
|
||||
TAG_NAME="v-test" \
|
||||
"${CHECKOUT}/scripts/release-all.sh" >"${TEST_TMP}/stdout.log" 2>"${TEST_TMP}/stderr.log"
|
||||
```
|
||||
|
||||
Update the wiki log expectations from:
|
||||
|
||||
```bash
|
||||
grep -Fxq "deploy-wiki --dry-run --create" "${TEST_TMP}/wiki.log" \
|
||||
|| fail "wiki dry run was not invoked"
|
||||
grep -Fxq "deploy-wiki --create" "${TEST_TMP}/wiki.log" \
|
||||
|| fail "wiki deploy was not invoked"
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```bash
|
||||
grep -Fxq "deploy-wiki --dry-run --create --stale-policy purge" "${TEST_TMP}/wiki.log" \
|
||||
|| fail "wiki stale purge dry run was not invoked"
|
||||
grep -Fxq "deploy-wiki --create --stale-policy purge" "${TEST_TMP}/wiki.log" \
|
||||
|| fail "wiki stale purge deploy was not invoked"
|
||||
```
|
||||
|
||||
Update the API ordering assertion to match the new dry-run log line:
|
||||
|
||||
```awk
|
||||
$0 == "WIKI deploy-wiki --dry-run --create --stale-policy purge" { wiki_dry_run = NR }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the release wrapper test to verify it fails**
|
||||
|
||||
Run from `../module`:
|
||||
|
||||
```bash
|
||||
./tests/release-all-cache-cleanup.sh
|
||||
```
|
||||
|
||||
Expected: FAIL because `scripts/release-all.sh` rejects `SOW_MODULE_WIKI_DEPLOY_STALE_POLICY=purge`.
|
||||
|
||||
- [ ] **Step 3: Update release wrapper validation**
|
||||
|
||||
In `../module/scripts/release-all.sh`, change:
|
||||
|
||||
```bash
|
||||
if [[ "${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}" != "report" && "${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}" != "archive" ]]; then
|
||||
echo "Unsupported SOW_MODULE_WIKI_DEPLOY_STALE_POLICY=${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}; expected report or archive" >&2
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
|
||||
to:
|
||||
|
||||
```bash
|
||||
if [[ "${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}" != "report" && "${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}" != "archive" && "${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}" != "purge" ]]; then
|
||||
echo "Unsupported SOW_MODULE_WIKI_DEPLOY_STALE_POLICY=${SOW_MODULE_WIKI_DEPLOY_STALE_POLICY}; expected report, archive, or purge" >&2
|
||||
return 1
|
||||
fi
|
||||
```
|
||||
|
||||
The existing dry-run/live deploy argument plumbing already forwards every non-`report` stale policy through `--stale-policy`.
|
||||
|
||||
- [ ] **Step 4: Run the release wrapper test**
|
||||
|
||||
Run from `../module`:
|
||||
|
||||
```bash
|
||||
./tests/release-all-cache-cleanup.sh
|
||||
```
|
||||
|
||||
Expected: PASS and both release wiki deploy phases include `--stale-policy purge`.
|
||||
|
||||
- [ ] **Step 5: Document release controls**
|
||||
|
||||
In `../module/README.md`, revise the environment variable bullet to name both destructive modes explicitly:
|
||||
|
||||
```markdown
|
||||
- `SOW_MODULE_WIKI_DEPLOY_STALE_POLICY` (defaults to `report`; use `archive`
|
||||
after a controlled dry run to retire stale generated pages in place, or
|
||||
`purge` after a controlled dry run to permanently remove stale generated
|
||||
topics already tracked in the wiki deploy manifest)
|
||||
```
|
||||
|
||||
In the wiki deployment paragraph, replace the archive-only sentence with a sentence that keeps these boundaries:
|
||||
|
||||
- `report` is default
|
||||
- `archive` rewrites stale generated pages in place
|
||||
- `purge` permanently removes only manifest-tracked stale generated topics
|
||||
- manual NodeBB wiki pages are not purge candidates
|
||||
|
||||
- [ ] **Step 6: Document local deploy controls**
|
||||
|
||||
In `../module/topdata/README.md`, replace:
|
||||
|
||||
```markdown
|
||||
reports stale manifest entries by default and can archive them in place with `--stale-policy archive`; it does not delete topics
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```markdown
|
||||
reports stale manifest entries by default, can archive them in place with
|
||||
`--stale-policy archive`, and can permanently purge only manifest-tracked stale
|
||||
generated topics with `--stale-policy purge` after a dry run
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Check module diffs**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
git -C ../module diff --check
|
||||
git -C ../module diff -- scripts/release-all.sh tests/release-all-cache-cleanup.sh README.md topdata/README.md
|
||||
```
|
||||
|
||||
Expected: no whitespace failures; diff shows only wrapper policy vocabulary, wrapper coverage, and docs.
|
||||
|
||||
- [ ] **Step 8: Commit module wrapper/docs**
|
||||
|
||||
Run from `../module`:
|
||||
|
||||
```bash
|
||||
git add scripts/release-all.sh tests/release-all-cache-cleanup.sh README.md topdata/README.md
|
||||
git commit -m "docs: allow generated wiki stale purges"
|
||||
```
|
||||
|
||||
## Task 6: Full Verification And Operator Notes
|
||||
|
||||
**Files:**
|
||||
- Verify: toolkit and module working trees
|
||||
|
||||
- [ ] **Step 1: Format Go changes**
|
||||
|
||||
Run from toolkit:
|
||||
|
||||
```bash
|
||||
gofmt -w internal/project/project.go internal/project/project_test.go internal/topdata/wiki_deploy.go internal/topdata/wiki_deploy_test.go internal/app/app.go internal/app/app_test.go
|
||||
```
|
||||
|
||||
Expected: command exits 0.
|
||||
|
||||
- [ ] **Step 2: Run toolkit verification**
|
||||
|
||||
Run from toolkit:
|
||||
|
||||
```bash
|
||||
go test ./internal/project ./internal/topdata ./internal/app
|
||||
go test ./...
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Expected: all tests pass and diff check emits no whitespace errors.
|
||||
|
||||
- [ ] **Step 3: Build the toolkit development binary**
|
||||
|
||||
Run from toolkit:
|
||||
|
||||
```bash
|
||||
./build-tool.sh
|
||||
```
|
||||
|
||||
Expected: `tools/sow-toolkit` is rebuilt successfully for module wrapper verification.
|
||||
|
||||
- [ ] **Step 4: Run module verification with the development binary**
|
||||
|
||||
Run from module:
|
||||
|
||||
```bash
|
||||
SOW_TOOLS_DEV_BINARY=../toolkit/tools/sow-toolkit ./validate-topdata.sh
|
||||
SOW_TOOLS_DEV_BINARY=../toolkit/tools/sow-toolkit ./build-wiki.sh --force
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Expected: topdata validation and wiki build pass; module diff check emits no whitespace errors.
|
||||
|
||||
- [ ] **Step 5: Record the cleanup command in the final handoff**
|
||||
|
||||
The final implementation response must state that destructive cleanup is opt-in and show the operator sequence:
|
||||
|
||||
```bash
|
||||
./deploy-wiki.sh --dry-run --stale-policy purge
|
||||
./deploy-wiki.sh --stale-policy purge
|
||||
```
|
||||
|
||||
For tag-triggered module release automation, state the equivalent variable:
|
||||
|
||||
```bash
|
||||
SOW_MODULE_WIKI_DEPLOY_STALE_POLICY=purge
|
||||
```
|
||||
|
||||
The handoff must mention that purge targets only stale generated pages already tracked in the deploy manifest and permanently removes their NodeBB topics/posts.
|
||||
Reference in New Issue
Block a user