@aperant/framework 0.7.0 → 0.7.4
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/CHANGELOG.md +205 -0
- package/agents/apt-planner.md +12 -0
- package/agents/apt-pr-review-fixer.md +13 -9
- package/bin/apt-tools.mjs +7 -0
- package/dist/cli/ci-watch/stop-matrix.d.mts.map +1 -1
- package/dist/cli/ci-watch/stop-matrix.mjs +16 -0
- package/dist/cli/ci-watch/stop-matrix.mjs.map +1 -1
- package/dist/cli/commands/ci-watch.d.mts +11 -0
- package/dist/cli/commands/ci-watch.d.mts.map +1 -1
- package/dist/cli/commands/ci-watch.mjs +108 -2
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/features-audit.d.mts +24 -0
- package/dist/cli/commands/features-audit.d.mts.map +1 -1
- package/dist/cli/commands/features-audit.mjs +159 -5
- package/dist/cli/commands/features-audit.mjs.map +1 -1
- package/dist/cli/commands/health-check.d.mts +16 -0
- package/dist/cli/commands/health-check.d.mts.map +1 -1
- package/dist/cli/commands/health-check.mjs +118 -2
- package/dist/cli/commands/health-check.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts +9 -0
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +49 -4
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/modes.d.mts.map +1 -1
- package/dist/cli/commands/modes.mjs +11 -0
- package/dist/cli/commands/modes.mjs.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.d.mts +13 -0
- package/dist/cli/commands/pr-review-audit-fixer.d.mts.map +1 -1
- package/dist/cli/commands/pr-review-audit-fixer.mjs +18 -5
- package/dist/cli/commands/pr-review-audit-fixer.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +84 -5
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/commands/validate-evidence.d.mts +24 -2
- package/dist/cli/commands/validate-evidence.d.mts.map +1 -1
- package/dist/cli/commands/validate-evidence.mjs +154 -17
- package/dist/cli/commands/validate-evidence.mjs.map +1 -1
- package/dist/cli/commands/vitest-doctor.d.mts +2 -0
- package/dist/cli/commands/vitest-doctor.d.mts.map +1 -0
- package/dist/cli/commands/vitest-doctor.mjs +168 -0
- package/dist/cli/commands/vitest-doctor.mjs.map +1 -0
- package/dist/cli/config/gitignore-drift.d.mts +23 -1
- package/dist/cli/config/gitignore-drift.d.mts.map +1 -1
- package/dist/cli/config/gitignore-drift.mjs +81 -3
- package/dist/cli/config/gitignore-drift.mjs.map +1 -1
- package/dist/cli/config/load.d.mts +56 -2
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +192 -2
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/consistency/parse-review.mjs +7 -0
- package/dist/cli/consistency/parse-review.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +24 -2
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.d.mts +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.d.mts.map +1 -1
- package/dist/cli/gate/gates/gitignore-in-sync.mjs +5 -2
- package/dist/cli/gate/gates/gitignore-in-sync.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts +5 -1
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +23 -18
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +49 -1
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +93 -14
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +7 -2
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/install/runtime-detect.d.mts +13 -0
- package/dist/cli/install/runtime-detect.d.mts.map +1 -1
- package/dist/cli/install/runtime-detect.mjs +9 -0
- package/dist/cli/install/runtime-detect.mjs.map +1 -1
- package/dist/cli/task/index-md.d.mts.map +1 -1
- package/dist/cli/task/index-md.mjs +14 -2
- package/dist/cli/task/index-md.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +11 -2
- package/dist/plugin/agents/apt-improver.md +99 -0
- package/dist/plugin/agents/apt-planner.md +127 -10
- package/dist/plugin/agents/apt-pr-review-fixer.md +13 -9
- package/dist/plugin/skills/apt/SKILL.md +1 -0
- package/dist/plugin/skills/apt-close-task/SKILL.md +63 -1
- package/dist/plugin/skills/apt-debug/SKILL.md +39 -6
- package/dist/plugin/skills/apt-debug/appendices/diagnose-discipline.md +119 -0
- package/dist/plugin/skills/apt-diagram/SKILL.md +378 -0
- package/dist/plugin/skills/apt-diagram/appendices/design-discipline.md +97 -0
- package/dist/plugin/skills/apt-discuss/SKILL.md +72 -5
- package/dist/plugin/skills/apt-discuss/appendices/grill-discipline.md +104 -0
- package/dist/plugin/skills/apt-discuss/appendices/zoom-out-helper.md +79 -0
- package/dist/plugin/skills/apt-execute/SKILL.md +57 -5
- package/dist/plugin/skills/apt-execute/appendices/tdd-mode.md +107 -0
- package/dist/plugin/skills/apt-improve/DEEPENING.md +84 -0
- package/dist/plugin/skills/apt-improve/INTERFACE-DESIGN.md +97 -0
- package/dist/plugin/skills/apt-improve/LANGUAGE.md +104 -0
- package/dist/plugin/skills/apt-improve/SKILL.md +141 -0
- package/dist/plugin/skills/apt-plan/SKILL.md +171 -4
- package/dist/plugin/skills/apt-plan/adapters/conductor.md +98 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +57 -18
- package/dist/plugin/skills/apt-prototype/LOGIC.md +109 -0
- package/dist/plugin/skills/apt-prototype/SKILL.md +143 -0
- package/dist/plugin/skills/apt-prototype/UI.md +90 -0
- package/dist/plugin/skills/apt-quick/SKILL.md +49 -8
- package/dist/plugin/skills/apt-release-notes/SKILL.md +193 -0
- package/dist/plugin/skills/apt-release-notes/appendices/persona-voice.md +59 -0
- package/dist/plugin/skills/apt-review/SKILL.md +2 -0
- package/dist/plugin/skills/apt-run/SKILL.md +32 -4
- package/dist/plugin/skills/apt-setup/SKILL.md +308 -6
- package/dist/plugin/skills/apt-ship/SKILL.md +122 -1
- package/dist/plugin/skills/apt-spar/SKILL.md +315 -0
- package/dist/plugin/skills/apt-triage/AGENT-BRIEF.md +84 -0
- package/dist/plugin/skills/apt-triage/OUT-OF-SCOPE.md +75 -0
- package/dist/plugin/skills/apt-triage/SKILL.md +169 -0
- package/dist/plugin/skills/apt-update/SKILL.md +77 -10
- package/dist/plugin/skills/apt-verify/SKILL.md +3 -0
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +10 -5
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +166 -0
- package/dist/plugin/skills/apt-zoom-out/SKILL.md +130 -0
- package/package.json +1 -1
- package/prompts/conductor-framework-context.md +63 -0
- package/prompts/conductor-system.md +11 -0
- package/skills/apt-close-task/SKILL.md +1 -0
- package/skills/apt-discuss/SKILL.md +47 -5
- package/skills/apt-execute/SKILL.md +9 -0
- package/skills/apt-plan/SKILL.md +12 -0
- package/skills/apt-pr-review/SKILL.md +19 -3
- package/skills/apt-quick/SKILL.md +19 -8
- package/skills/apt-researcher.md +1 -0
- package/skills/apt-setup/SKILL.md +33 -2
- package/skills/apt-verify-proof/SKILL.md +7 -5
- package/src/cli/ci-watch/stop-matrix.mjs +17 -0
- package/src/cli/commands/ci-watch.mjs +113 -2
- package/src/cli/commands/features-audit.mjs +164 -5
- package/src/cli/commands/health-check.mjs +115 -2
- package/src/cli/commands/init.mjs +52 -4
- package/src/cli/commands/modes.mjs +11 -0
- package/src/cli/commands/pr-review-audit-fixer.mjs +18 -5
- package/src/cli/commands/task.mjs +83 -5
- package/src/cli/commands/validate-evidence.mjs +158 -17
- package/src/cli/commands/vitest-doctor.mjs +173 -0
- package/src/cli/config/gitignore-drift.mjs +74 -3
- package/src/cli/config/load.mjs +188 -2
- package/src/cli/consistency/parse-review.mjs +6 -0
- package/src/cli/dispatch.mjs +23 -2
- package/src/cli/gate/gates/gitignore-in-sync.mjs +5 -2
- package/src/cli/gate/gates/review-clean.mjs +24 -19
- package/src/cli/gate/gates/verify-approved.mjs +97 -14
- package/src/cli/help.mjs +7 -2
- package/src/cli/install/runtime-detect.mjs +9 -0
- package/src/cli/task/index-md.mjs +15 -2
- package/templates/config.json +2 -7
- package/workflows/verify-proof.md +8 -3
|
@@ -17,7 +17,7 @@ execution_modes:
|
|
|
17
17
|
- step
|
|
18
18
|
- research
|
|
19
19
|
allowed-tools: "Bash, Read, Grep, Glob, Write"
|
|
20
|
-
argument-hint: "apt:discuss [--brainstorm] [topic]"
|
|
20
|
+
argument-hint: "apt:discuss [--brainstorm] [--grill] [topic]"
|
|
21
21
|
gates: []
|
|
22
22
|
---
|
|
23
23
|
<objective>
|
|
@@ -183,10 +183,11 @@ If any file is missing, proceed with what's available. The framework works with
|
|
|
183
183
|
Extract the task description from `$ARGUMENTS`:
|
|
184
184
|
|
|
185
185
|
1. Strip mode flags (`--assumptions`, `--power`, `--auto`, `--review-auto`, `--continue`) from arguments
|
|
186
|
-
2.
|
|
187
|
-
3.
|
|
188
|
-
4. If
|
|
189
|
-
5.
|
|
186
|
+
2. Strip the **`--grill`** overlay flag from arguments (orthogonal to the mode flags — see Section 6.5). `--grill` may combine with any mode; standalone `--grill` defaults to Interactive.
|
|
187
|
+
3. The remaining text is the task description
|
|
188
|
+
4. If task description is empty but `state.json` has an `active_task`, use that task's description
|
|
189
|
+
5. If still empty, ask: "What are you working on?"
|
|
190
|
+
6. Generate a `task-id` slug from the description: lowercase, hyphens, max 40 characters
|
|
190
191
|
- Example: "Add OAuth login with Google" -> `add-oauth-login-with-google`
|
|
191
192
|
|
|
192
193
|
## 3.5. Brainstorm (skip if you picked decision-lock in Section 1)
|
|
@@ -564,6 +565,47 @@ Review-Auto is Auto Mode **plus one human checkpoint at the batch end**. It does
|
|
|
564
565
|
|
|
565
566
|
Do NOT introduce per-decision staging ("show me D-01, wait, show me D-02, wait...") — that collapses review-auto into spaced interactive mode and defeats the point of joint reasoning up front. Do NOT skip the pause ("autonomy is high, let's just write it") — that collapses review-auto into auto. The one pause at the end is the whole identity of this mode.
|
|
566
567
|
|
|
568
|
+
## 6.5. Grill Mode (`--grill` flag — Pocock `grill-with-docs` overlay)
|
|
569
|
+
|
|
570
|
+
**Activate when:** the user invoked the skill with `--grill`. This is an **overlay**, not a mode — it stacks on top of whichever Section 6 mode is active (Interactive by default; combine with `--auto` etc. as desired). The overlay does two things: (1) it applies Pocock's 4 grill moves during decision resolution; (2) it fires inline writes to CONTEXT.md and ADRs as terms resolve and decisions pass the Nygard 3-gate.
|
|
571
|
+
|
|
572
|
+
The discipline this overlay loads is already in [`appendices/grill-discipline.md`](./appendices/grill-discipline.md) — re-read the reframe sentence and the autonomy degradation table before each grill turn. **Do NOT duplicate that content here.** What follows is only the moves + writer-call instructions specific to the overlay.
|
|
573
|
+
|
|
574
|
+
### The 4 grill moves (apply during gray-area resolution)
|
|
575
|
+
|
|
576
|
+
1. **Challenge against the glossary.** When the user uses a term that conflicts with an existing CONTEXT.md term-block, call it out immediately: *"Your glossary defines 'cancellation' as X, but you seem to mean Y — which is it?"* Read `CONTEXT.md` (or per-context CONTEXT files via `CONTEXT-MAP.md`) when the overlay activates; skip silently if absent.
|
|
577
|
+
2. **Sharpen fuzzy language.** When the user uses vague or overloaded terms, propose a precise canonical term: *"You're saying 'account' — do you mean Customer or User? Those are different things."*
|
|
578
|
+
3. **Stress-test with concrete scenarios.** When domain relationships are being discussed, invent specific scenarios that probe edge cases and force precision about the boundaries between concepts.
|
|
579
|
+
4. **Cross-reference with code.** When the user states how something works, check whether the code agrees. If you find a contradiction, surface it: *"Your code cancels entire Orders, but you just said partial cancellation is possible — which is right?"*
|
|
580
|
+
|
|
581
|
+
### Inline writer calls (NOT batched)
|
|
582
|
+
|
|
583
|
+
When a term resolves with confidence during the grill, invoke `apt-tools context write` to upsert the term-block into CONTEXT.md **right there** — do NOT batch:
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
node .aperant/deps/node_modules/@aperant/framework/bin/apt-tools.mjs context write . \
|
|
587
|
+
--term "Plan thread" \
|
|
588
|
+
--definition "<one-line definition>" \
|
|
589
|
+
--aliases-to-avoid "Plan session,plan chat" \
|
|
590
|
+
--cardinality "one-to-many"
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
**Lazy creation:** if no `CONTEXT.md` exists at the project root (or in the relevant per-context directory when `CONTEXT-MAP.md` is present), the first resolved term creates the file. Do NOT create CONTEXT.md upfront with placeholder content.
|
|
594
|
+
|
|
595
|
+
**Glossary-only rule:** CONTEXT.md is a glossary of domain terms — NO implementation details, NO spec content, NO scratch-pad notes. Implementation lives in `spec.md` / `implementation_plan.json`; scratch lives in `.aperant/context/notes/{task-id}.md`. Term-blocks are user-facing concepts (e.g. "Plan thread", "Sparring round"), not code identifiers (e.g. "ChatSession", "useTaskStore").
|
|
596
|
+
|
|
597
|
+
When a decision passes the **Nygard 3-gate**, offer to write an ADR via `apt-tools adr write .`. The 3-gate (ALL three must hold — miss one and skip the ADR):
|
|
598
|
+
|
|
599
|
+
1. **Hard to reverse** — the cost of changing your mind later is meaningful.
|
|
600
|
+
2. **Surprising without context** — a future reader will wonder *"why did they do it this way?"*.
|
|
601
|
+
3. **Result of a real trade-off** — there were genuine alternatives and you picked one for specific reasons.
|
|
602
|
+
|
|
603
|
+
If only one or two hold, the decision belongs in `.aperant/context/notes/{task-id}.md` (locked-decisions list), not in an ADR.
|
|
604
|
+
|
|
605
|
+
### Autonomy interaction
|
|
606
|
+
|
|
607
|
+
`--grill` does not change the autonomy degradation already specified in [`appendices/grill-discipline.md`](./appendices/grill-discipline.md). Autonomy 1 grills every term/decision interactively; Autonomy 2 batches recommendations; Autonomy 3 auto-locks with the three escalation triggers (conflict-with-locked, critical-path file, no-defensible-default). The overlay simply ensures that whichever autonomy path runs, the writer CLIs fire as side-effects of the resolution work.
|
|
608
|
+
|
|
567
609
|
## 7. Advanced Elicitation (DEEP Tasks Only)
|
|
568
610
|
|
|
569
611
|
After all decisions are locked, check the task track. If the track is DEEP (from state.json or router context), behavior depends on the resolved mode from Section 6's precedence chain (D-07):
|
|
@@ -383,11 +383,20 @@ executor commits introduce a new feature surface without registering it.
|
|
|
383
383
|
|
|
384
384
|
```bash
|
|
385
385
|
node packages/framework/bin/apt-tools.mjs features-audit . \
|
|
386
|
+
--task-id "$TASK_ID" \
|
|
386
387
|
--diff-files "$(git diff --name-only HEAD^ HEAD | tr '\n' ',')" \
|
|
387
388
|
--apply-stubs
|
|
388
389
|
```
|
|
389
390
|
|
|
390
391
|
Notes:
|
|
392
|
+
- `--task-id $TASK_ID` (BUG-025b) is mandatory when the executor runs
|
|
393
|
+
from the main-repo cwd instead of inside the worktree (the
|
|
394
|
+
orchestrator's common path). features-audit resolves the worktree's
|
|
395
|
+
`.aperant/features/` from the active task record and writes there,
|
|
396
|
+
preventing generated `*.json` from leaking into main. When invoked
|
|
397
|
+
from the worktree cwd directly, `--task-id` is a no-op (layer 1
|
|
398
|
+
detection short-circuits). Pass `--no-worktree-redirect` only when
|
|
399
|
+
you intentionally want to write to the invoking cwd.
|
|
391
400
|
- `--apply-stubs` only runs when the matched surface's policy is
|
|
392
401
|
`auto-update` (the default for `.aperant/features/*.json`). Users who
|
|
393
402
|
opted out via `/apt:setup` Batch 7 get a no-op — the command still
|
package/skills/apt-plan/SKILL.md
CHANGED
|
@@ -99,6 +99,18 @@ For STANDARD and COMPLEX tasks:
|
|
|
99
99
|
- Test files that will need updates
|
|
100
100
|
- Configuration that may need changes
|
|
101
101
|
|
|
102
|
+
### 4.4. React state guidance — sync-store vs render-committed ref (BUG-025-planner)
|
|
103
|
+
|
|
104
|
+
When a planned event handler fires a flush/persist immediately after mutating a Zustand/Jotai/refs-stored store, the spec MUST recommend reading from `store.getState()` (or the equivalent direct-read primitive) on the synchronous path, NOT from a `useRef`-populated-by-`useEffect`.
|
|
105
|
+
|
|
106
|
+
Why: refs populated via `useEffect` lag the store by exactly one render cycle. A handler that mutates the store and immediately reads the ref sees the pre-mutation value, silently corrupting persists and order-of-operations contracts. Reference pattern: `apps/desktop/src/renderer/src/App.tsx::runUpsertForIds` (post-fix commit `4c263856`).
|
|
107
|
+
|
|
108
|
+
Investigation checklist when the task touches a Zustand/Jotai store or any handler that does flush-after-mutate:
|
|
109
|
+
|
|
110
|
+
- Grep for `useRef(` references that cache store state (e.g. `const fooRef = useRef(foo)` populated by `useEffect(() => { fooRef.current = foo }, [foo])`).
|
|
111
|
+
- If the spec or planned subtasks read from such a ref on a synchronous path that ALSO mutates the store, recommend `store.getState()` (or `useFooStore.getState()`) as the read primitive.
|
|
112
|
+
- Note the caveat explicitly in the spec's Implementation Decisions section so the executor doesn't silently revert it.
|
|
113
|
+
|
|
102
114
|
### 4.5. Consume design.md (C24)
|
|
103
115
|
|
|
104
116
|
If `{task_dir}/design.md` exists (emitted by a prior `/apt:design` run), load it before decomposing subtasks:
|
|
@@ -257,7 +257,7 @@ When the result is `unknown`, treat it as `external` for safety and prompt the u
|
|
|
257
257
|
Authorship could not be auto-detected for PR #${PR_NUMBER}. Treat as [owned / external]? (default: external)
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
-
If `autonomy_level >= 3` (yolo), skip the prompt and lock in `external`. Persist the user's answer to
|
|
260
|
+
If `autonomy_level >= 3` (yolo), skip the prompt and lock in `external`. Persist the user's answer to `pr-state.json:authorship` so subsequent rounds don't re-ask. (Per C56 A3 — authorship overrides moved from `config.json:pr_review.authorship_overrides` to per-PR `pr-state.json` so they GC with the pr-review round. Legacy `config.json` entries are still read with a deprecation warning + auto-promoted to pr-state on next `init-pr`; do NOT write to `config.json` from new code.)
|
|
261
261
|
|
|
262
262
|
Store the result as `AUTHORSHIP_MODE` and use it as a gate at every later phase:
|
|
263
263
|
|
|
@@ -1110,6 +1110,8 @@ For `AUTHORSHIP_MODE == "owned"`, continue normally.
|
|
|
1110
1110
|
|
|
1111
1111
|
AI agents have near-zero marginal cost per fix. The framework fixes **all** confirmed findings — critical, high, medium, and low — regardless of autonomy level. There is no severity gate and no "report for manual resolution" path. If a finding was confirmed by the validator, it gets fixed.
|
|
1112
1112
|
|
|
1113
|
+
**Hard rule — context budget is NOT a valid skip reason.** The orchestrator MAY NOT defer, drop, downgrade, or "punt for follow-up" any confirmed finding because its own context window feels heavy, because the run has already produced many fixes, or because it judges marginal value low. The query loop auto-compacts on its own — trust it. Per-finding fix work happens inside fix agents (separate context windows), so orchestrator pressure does not justify cutting their inputs. The only valid skip reasons are the ones enumerated in Key Principle #2 (`fixable: false`, critical finding without user approval, file assigned to another fixer). Any other skip is an unauthorized policy deviation — emit all confirmed findings to fixers, then let the audit gate adjudicate outcomes.
|
|
1114
|
+
|
|
1113
1115
|
| Autonomy | Action |
|
|
1114
1116
|
|----------|--------|
|
|
1115
1117
|
| `< 2` | Show findings summary before fixing. Then proceed with auto-fix. |
|
|
@@ -1195,6 +1197,12 @@ Take all confirmed, fixable findings and group them into up to 3 independent gro
|
|
|
1195
1197
|
|
|
1196
1198
|
#### Step 3: Spawn Fix Agents
|
|
1197
1199
|
|
|
1200
|
+
**Capture the iteration-start SHA BEFORE spawning fixers** (used by both the Step 4b audit gate AND the per-fixer Pre-Return Self-Audit added in BUG-016 — fixers now commit atomically per finding, and the audit window is scoped by this SHA):
|
|
1201
|
+
|
|
1202
|
+
```bash
|
|
1203
|
+
ITERATION_START_SHA=$(cd "${WORKTREE_PATH}" && git rev-parse HEAD)
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1198
1206
|
For each fix group, spawn a fix agent. Read the agent definition from `.claude/agents/apt-pr-review-fixer.md` and fill in:
|
|
1199
1207
|
|
|
1200
1208
|
| Placeholder | Value |
|
|
@@ -1207,6 +1215,7 @@ For each fix group, spawn a fix agent. Read the agent definition from `.claude/a
|
|
|
1207
1215
|
| `[CRITICAL_APPROVAL]` | `true` or `false` for each critical finding in this group |
|
|
1208
1216
|
| `[PARALLEL_FIXERS]` | `true` if more than 1 fixer is spawned this iteration |
|
|
1209
1217
|
| `[WORKTREE_PATH]` | The worktree path — fix agents edit files HERE, not in the user's working directory |
|
|
1218
|
+
| `[ITERATION_START_SHA]` | The SHA captured BEFORE spawning fixers (see Step 4b's `ITERATION_START_SHA=$(...)` capture). Required for BUG-016 commit-discipline Pre-Return Self-Audit. |
|
|
1210
1219
|
|
|
1211
1220
|
```
|
|
1212
1221
|
Agent(
|
|
@@ -1260,7 +1269,9 @@ If `--since-sha` is **omitted**, the CLI falls back to `git merge-base HEAD orig
|
|
|
1260
1269
|
|
|
1261
1270
|
Rule of thumb: pass `--since-sha "${ITERATION_START_SHA}"` on every audit invocation. The merge-base fallback exists only so a missing flag doesn't flip every standard-branch-workflow repo into a false-positive hallucination — it is NOT a replacement for the explicit flag.
|
|
1262
1271
|
|
|
1263
|
-
The CLI returns a JSON envelope with `verdict` ∈ `verified | hallucinated | skipped`, writes `iterations/${ITERATION}/audit.json` (append-only), and bumps `metadata.json.fixer_hallucinations_total` on every `hallucinated` outcome. The envelope's `observed.since_sha` + `observed.since_sha_source` fields record which SHA was used and where it came from (`flag` | `merge-base` | `null`). The same envelope is used for the fixer and the self-reviewer (Phase 7) — `--fixer self-reviewer` reads from `self-review.md
|
|
1272
|
+
The CLI returns a JSON envelope with `verdict` ∈ `verified | hallucinated | skipped`, writes `iterations/${ITERATION}/audit.json` (append-only), and bumps `metadata.json.fixer_hallucinations_total` on every `hallucinated` outcome. The envelope's `observed.since_sha` + `observed.since_sha_source` fields record which SHA was used and where it came from (`flag` | `merge-base` | `null`). The same envelope is used for the fixer and the self-reviewer (Phase 7) — `--fixer self-reviewer` reads from `self-review.md`.
|
|
1273
|
+
|
|
1274
|
+
**Canonical fix-report filename per fixer (BUG-030):** each fixer agent writes to `fixes-applied-${FIXER_NAME}.md` (e.g. `fixes-applied-fixer-1.md`). The audit CLI resolves candidates in strict order: `fixes-applied-group{N}.md` (legacy group-named, pre-BUG-030 reviews) → `fixes-applied-fixer-{N}.md` (NEW fixer-named) → `fixes-applied-${FIXER_NAME}.md` (exact match, preferred for non-numeric fixer names) → `fixes-applied.md` (legacy unscoped, single-fixer reviews only). Writing the canonical name eliminates the cross-fixer false-positive `hallucinated` verdict that fired when two parallel fixers shared `fixes-applied.md`.
|
|
1264
1275
|
|
|
1265
1276
|
| Verdict | Orchestrator Action |
|
|
1266
1277
|
|---------|---------------------|
|
|
@@ -1638,7 +1649,7 @@ node packages/framework/bin/apt-tools.mjs workflow destroy . --id ${REVIEW_ID}
|
|
|
1638
1649
|
Agents do NOT assign confidence percentages. Either they have evidence (actual code snippet from Read tool) or they do not have a finding. The validator independently verifies every finding against real code.
|
|
1639
1650
|
|
|
1640
1651
|
### 2. Fix Everything Found
|
|
1641
|
-
If it was found and confirmed, fix it. ALL severities. "This is minor" is NOT a valid skip reason. The only valid skip reasons are: `fixable: false`, critical findings without user approval, or files assigned to another fixer.
|
|
1652
|
+
If it was found and confirmed, fix it. ALL severities. "This is minor" is NOT a valid skip reason. **Neither is "my context window is heavy" or "I've already done a lot this run"** — orchestrator context pressure never authorizes dropping a confirmed finding (see Phase 6 → Principle: Fix Everything). The only valid skip reasons are: `fixable: false`, critical findings without user approval, or files assigned to another fixer.
|
|
1642
1653
|
|
|
1643
1654
|
### 3. Findings Flow Through Files
|
|
1644
1655
|
Agents write to `.aperant/pr-reviews/{review-id}/findings/`. The orchestrator reads with the Read tool. Fix agents read finding files directly. This keeps the orchestrator context under 30%.
|
|
@@ -1702,6 +1713,11 @@ Fix agents self-report their outcome via a status line (`FIXED: X | FAILED: Y |
|
|
|
1702
1713
|
**Wrong:** Skipping Phase 6 entirely when autonomy < 2.
|
|
1703
1714
|
**Right:** Always fix all confirmed findings. At autonomy < 2, show findings summary first, then proceed with auto-fix. The only difference is whether findings are displayed before fixing — not whether they get fixed.
|
|
1704
1715
|
|
|
1716
|
+
### Mistake 7a: Dropping Findings Because Orchestrator Context Feels Heavy
|
|
1717
|
+
|
|
1718
|
+
**Wrong:** Mid-Phase-6, the orchestrator decides "my window is getting heavy after 6 specialist rounds + Round 1 fixes — I'll skip the minor findings and report a partial fix." This is an unauthorized policy deviation, even when phrased as "judgment call" or "context-budget reasons."
|
|
1719
|
+
**Right:** Spawn fix agents for **every** confirmed finding regardless of orchestrator context state. Fix work happens in the fixer's window, not yours. The query loop auto-compacts when it needs to — trust it. If you genuinely cannot fit the spawn calls, that is a framework bug to file (not a license to ship a partial fix); emit all findings to fixers in the smallest batches possible and let the audit gate adjudicate. Severity, "diminishing returns," and your own token pressure are NOT skip authorities — only the three reasons in Key Principle #2 are.
|
|
1720
|
+
|
|
1705
1721
|
### Mistake 8: Forgetting to Update metadata.json
|
|
1706
1722
|
|
|
1707
1723
|
**Wrong:** Writing review.json but leaving metadata.json stuck at `"status": "in-progress"`.
|
|
@@ -279,18 +279,29 @@ skill MUST exit silently regardless of the config value, so spawned subagents
|
|
|
279
279
|
never block on a prompt that no human will answer.
|
|
280
280
|
|
|
281
281
|
```bash
|
|
282
|
-
# Pseudocode the model executes inline
|
|
282
|
+
# Pseudocode the model executes inline.
|
|
283
|
+
# AUDIT-001: `preferences.quick_task_post_verify` is per-device — read from
|
|
284
|
+
# the MERGED config (shared `.aperant/config.json` + local
|
|
285
|
+
# `.aperant/config.local.json`). Local wins on collision. Reading raw
|
|
286
|
+
# `config.json` only would miss per-device overrides.
|
|
283
287
|
node -e '
|
|
284
288
|
const fs = require("fs");
|
|
285
289
|
const path = require("path");
|
|
286
|
-
const
|
|
290
|
+
const readJsonOrEmpty = (p) => { try { return JSON.parse(fs.readFileSync(p, "utf-8")) ?? {} } catch { return {} } };
|
|
291
|
+
const shared = readJsonOrEmpty(path.join(process.cwd(), ".aperant/config.json"));
|
|
292
|
+
const local = readJsonOrEmpty(path.join(process.cwd(), ".aperant/config.local.json"));
|
|
293
|
+
function merge(a, b) {
|
|
294
|
+
if (a === null || typeof a !== "object" || Array.isArray(a)) return b;
|
|
295
|
+
if (b === null || typeof b !== "object" || Array.isArray(b)) return b;
|
|
296
|
+
const out = { ...a };
|
|
297
|
+
for (const k of Object.keys(b)) out[k] = (k in a) ? merge(a[k], b[k]) : b[k];
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
300
|
+
const cfg = merge(shared, local);
|
|
287
301
|
let mode = "ask";
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
mode = cfg.preferences.quick_task_post_verify;
|
|
292
|
-
}
|
|
293
|
-
} catch {}
|
|
302
|
+
if (cfg.preferences && cfg.preferences.quick_task_post_verify) {
|
|
303
|
+
mode = cfg.preferences.quick_task_post_verify;
|
|
304
|
+
}
|
|
294
305
|
if (mode === "never" || !process.stdin.isTTY) process.exit(0);
|
|
295
306
|
if (mode === "always") { /* invoke apt:verify {task-id} */ process.exit(0); }
|
|
296
307
|
// mode === "ask": readline prompt with 30s timeout, default = skip.
|
package/skills/apt-researcher.md
CHANGED
|
@@ -41,6 +41,7 @@ Parse the investigation query. Identify:
|
|
|
41
41
|
3. Read key files to understand the full picture
|
|
42
42
|
4. Follow import chains to map dependencies
|
|
43
43
|
5. Check tests for behavioral documentation
|
|
44
|
+
6. **Report which refs cache store state on a render-cycle delay** (BUG-025-planner): when investigating React state or any Zustand/Jotai store touched by handlers, grep for `useRef(` patterns populated via `useEffect`. Flag them explicitly — they lag the store by one render and any sync-mutate-then-read code path against them silently corrupts. Reference pattern: `apps/desktop/src/renderer/src/App.tsx::runUpsertForIds` (post-fix commit `4c263856`).
|
|
44
45
|
|
|
45
46
|
## 3. Report Findings
|
|
46
47
|
Produce a clear, structured answer:
|
|
@@ -1111,6 +1111,34 @@ The selective-edit pass through Batch 9 MUST respect the §1b dispatch — it do
|
|
|
1111
1111
|
|
|
1112
1112
|
## 4. Map Answers to Config
|
|
1113
1113
|
|
|
1114
|
+
**AUDIT-001 — file-routing contract for per-device fields (UPDATED 0.7.2):** the
|
|
1115
|
+
following five fields are PER-DEVICE and MUST land in `.aperant/config.local.json`
|
|
1116
|
+
(gitignored), NOT the shared `.aperant/config.json`:
|
|
1117
|
+
- `verification.open_report` — covered in the "Auto-Open" mapping below.
|
|
1118
|
+
- `multi_model.cross_verification.tools` — covered in the "Cross-Verification" mapping below.
|
|
1119
|
+
- `preferences.date_format` — covered in the "Date Format" mapping below.
|
|
1120
|
+
- `preferences.time_format` — covered in the "Time Format" mapping below.
|
|
1121
|
+
- `preferences.quick_task_post_verify` — covered in apt-quick SKILL config block.
|
|
1122
|
+
|
|
1123
|
+
All other fields (including `verification.evidence_capture`,
|
|
1124
|
+
`multi_model.cross_verification.enabled`, `multi_model.video_review.*`,
|
|
1125
|
+
`preferences.task_index`, every `orchestration.*` value) stay in shared
|
|
1126
|
+
`.aperant/config.json`.
|
|
1127
|
+
|
|
1128
|
+
Implementation: use the framework's `writeLocalConfigField(targetDir, dotPath, value)`
|
|
1129
|
+
helper from `packages/framework/src/cli/config/load.mjs` (or its shell-out
|
|
1130
|
+
equivalent via `apt-tools config write . --local --key <dotpath> --value <json>`)
|
|
1131
|
+
for the five fields above. NEVER hand-roll `JSON.parse(readFileSync('.aperant/config.json'))`
|
|
1132
|
+
followed by `writeFileSync('.aperant/config.json', ...)` for these fields — it
|
|
1133
|
+
will land per-device data in the shared file and re-introduce the
|
|
1134
|
+
shared-config trap AUDIT-001 was designed to close.
|
|
1135
|
+
|
|
1136
|
+
Reads of these fields anywhere in the framework MUST go through
|
|
1137
|
+
`loadMergedProjectConfig(targetDir)` or `readLocalOrSharedField(targetDir, dotPath)`
|
|
1138
|
+
so per-device overrides are honored.
|
|
1139
|
+
|
|
1140
|
+
---
|
|
1141
|
+
|
|
1114
1142
|
Map the user's selections to config values:
|
|
1115
1143
|
|
|
1116
1144
|
**Verify Mode:**
|
|
@@ -1180,13 +1208,16 @@ Note: task ids are slug-first since v0.6.6 — the date-format setting controls
|
|
|
1180
1208
|
|
|
1181
1209
|
## 5. Write Config
|
|
1182
1210
|
|
|
1183
|
-
Write merged config to
|
|
1211
|
+
Write merged config to two files per AUDIT-001 routing (see Step 4 block):
|
|
1212
|
+
|
|
1213
|
+
- Shared `.aperant/config.json` — all project policy values (the bulk of the answers).
|
|
1214
|
+
- Per-device `.aperant/config.local.json` — the five AUDIT-001 fields enumerated in Step 4.
|
|
1184
1215
|
|
|
1185
1216
|
```bash
|
|
1186
1217
|
mkdir -p .aperant
|
|
1187
1218
|
```
|
|
1188
1219
|
|
|
1189
|
-
Write
|
|
1220
|
+
Write each JSON file with 2-space indentation. Bump shared `config.json`'s `version` to 4 if upgrading from an older config (versions 2/3 load forward-compatibly — the new `tdd` block is optional). `config.local.json` is gitignored by `.aperant/.gitignore` — do not check it in.
|
|
1190
1221
|
|
|
1191
1222
|
## 6. Save as Global Defaults (Optional)
|
|
1192
1223
|
|
|
@@ -89,8 +89,10 @@ For multi-model verification details:
|
|
|
89
89
|
|
|
90
90
|
## Config
|
|
91
91
|
|
|
92
|
-
Settings in `.aperant/config.json`
|
|
93
|
-
|
|
94
|
-
- `verification.
|
|
95
|
-
- `
|
|
96
|
-
- `multi_model.
|
|
92
|
+
Settings live in the MERGED Aperant config (shared `.aperant/config.json` + per-device `.aperant/config.local.json`; local wins). Run `/apt:setup` to configure. Read via `apt-tools config read .` or the framework's `loadMergedProjectConfig()` — NEVER raw `JSON.parse(readFileSync('.aperant/config.json'))`, which misses per-device overrides.
|
|
93
|
+
|
|
94
|
+
- `verification.evidence_capture` — shared. capture screenshots + terminal output (default: true)
|
|
95
|
+
- `verification.open_report` — **per-device (config.local.json)** per AUDIT-001. open HTML in browser when done (default: true)
|
|
96
|
+
- `multi_model.enabled` — shared. run multi-model verification (default: false)
|
|
97
|
+
- `multi_model.cross_verification.tools` — **per-device (config.local.json)** per AUDIT-001. which CLIs to cross-verify with.
|
|
98
|
+
- `multi_model.gemini.model` — shared. Gemini preset: default/thorough/fast
|
|
@@ -106,6 +106,23 @@ export function classify({ checks, state, prMetadata }) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
// 6b. BUG-031 defensive wait branch — if no checks are actually
|
|
110
|
+
// failing (everything is success / skipped / in_progress / neutral),
|
|
111
|
+
// never fall through to the default-red 'fix' at the bottom. Placed
|
|
112
|
+
// AFTER reviewer-pause so CHANGES_REQUESTED + no-failures still
|
|
113
|
+
// produces 'reviewer-pause' (locks existing semantics), BEFORE the
|
|
114
|
+
// remaining checks (identical-log, flake-skip, infra-backoff, fix)
|
|
115
|
+
// which all assume at least one failing row. Pre-fix, a PR with a
|
|
116
|
+
// success + a SKIPPED conditional CI job fell through to 'fix' and
|
|
117
|
+
// burned a fix attempt every tick on a non-existent failure.
|
|
118
|
+
if (failed.length === 0) {
|
|
119
|
+
return {
|
|
120
|
+
action: 'wait',
|
|
121
|
+
counts_toward_budget: false,
|
|
122
|
+
reason: 'no failing checks; awaiting in-flight or skipped',
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
109
126
|
// 7. Identical-log → non-converging fixer.
|
|
110
127
|
// Compare aggregate fingerprints (same format as the write side in ci-watch.mjs).
|
|
111
128
|
if (attempts > 0 && st.last_status && failed.length > 0) {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
* itself never shells out to gh.
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
+
import { spawnSync } from 'node:child_process'
|
|
27
28
|
import { randomBytes } from 'node:crypto'
|
|
28
29
|
import { existsSync, readFileSync } from 'node:fs'
|
|
29
30
|
import { dirname, resolve } from 'node:path'
|
|
@@ -94,7 +95,7 @@ function validatePr(pr) {
|
|
|
94
95
|
*/
|
|
95
96
|
export function cmdCiWatch(subcommand, projectDir, extraArgs) {
|
|
96
97
|
if (!subcommand) {
|
|
97
|
-
return err('Usage: apt-tools ci-watch <start|stop|status|tick> <project-dir> [flags]')
|
|
98
|
+
return err('Usage: apt-tools ci-watch <start|stop|status|tick|sweep> <project-dir> [flags]')
|
|
98
99
|
}
|
|
99
100
|
if (!projectDir) {
|
|
100
101
|
return err(`Usage: apt-tools ci-watch ${subcommand} <project-dir> [flags]`)
|
|
@@ -112,9 +113,34 @@ export function cmdCiWatch(subcommand, projectDir, extraArgs) {
|
|
|
112
113
|
return doStatus(root, flags)
|
|
113
114
|
case 'tick':
|
|
114
115
|
return doTick(root, flags)
|
|
116
|
+
case 'sweep':
|
|
117
|
+
return err(
|
|
118
|
+
'sweep is async; call cmdCiWatchSweep directly (the apt-tools dispatcher routes it separately)',
|
|
119
|
+
)
|
|
115
120
|
default:
|
|
116
|
-
return err(
|
|
121
|
+
return err(
|
|
122
|
+
`Unknown ci-watch subcommand: ${subcommand}. Expected: start, stop, status, tick, sweep`,
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Async sibling for the `sweep` subcommand (the rate-limited orphaned-state
|
|
129
|
+
* walker). Split out from the sync `cmdCiWatch` dispatcher so the latter's
|
|
130
|
+
* return type stays `CommandResult` (not a `CommandResult | Promise` union)
|
|
131
|
+
* and the synchronous tests + callers don't need to be wrapped in `await`.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} projectDir
|
|
134
|
+
* @param {string[]} extraArgs
|
|
135
|
+
* @returns {Promise<CommandResult>}
|
|
136
|
+
*/
|
|
137
|
+
export async function cmdCiWatchSweep(projectDir, extraArgs) {
|
|
138
|
+
if (!projectDir) {
|
|
139
|
+
return err('Usage: apt-tools ci-watch sweep <project-dir> [flags]')
|
|
117
140
|
}
|
|
141
|
+
const root = resolve(projectDir)
|
|
142
|
+
const flags = parseFlags(extraArgs)
|
|
143
|
+
return await doSweep(root, flags)
|
|
118
144
|
}
|
|
119
145
|
|
|
120
146
|
// ---------------------------------------------------------------------------
|
|
@@ -245,6 +271,91 @@ function doStop(root, flags) {
|
|
|
245
271
|
})
|
|
246
272
|
}
|
|
247
273
|
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// sweep — BUG-027 (c): manual GC for orphaned ci-watch state files.
|
|
276
|
+
//
|
|
277
|
+
// Enumerates all watcher state files via `listWatches(root)`, shells out
|
|
278
|
+
// to `gh pr view --json state` for each one (rate-limited to ≤5/sec),
|
|
279
|
+
// and removes the state files whose PR state is MERGED or CLOSED. Never
|
|
280
|
+
// auto-runs on startup — opt-in manual subcommand only, per brief.
|
|
281
|
+
// Honors APT_GH_FIXTURE_JSON for hermetic tests (returns the same body
|
|
282
|
+
// for every PR — fine since the test fixtures we ship cover each
|
|
283
|
+
// branch in isolation).
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
|
|
286
|
+
async function doSweep(root, flags) {
|
|
287
|
+
if (flags.get('orphaned') !== true) {
|
|
288
|
+
return err(
|
|
289
|
+
'ci-watch sweep currently supports only `--orphaned`. Usage: apt-tools ci-watch sweep <project-dir> --orphaned',
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
const dryRun = flags.get('dry-run') === true
|
|
293
|
+
const watches = listWatches(root)
|
|
294
|
+
const removed = []
|
|
295
|
+
const skipped = []
|
|
296
|
+
const ghFixture = process.env.APT_GH_FIXTURE_JSON
|
|
297
|
+
for (let i = 0; i < watches.length; i++) {
|
|
298
|
+
const watch = watches[i]
|
|
299
|
+
const pr = watch.pr
|
|
300
|
+
const prUrl = watch.pr_url ?? null
|
|
301
|
+
// Validate pr_url shape before passing to gh: must be an https:// URL
|
|
302
|
+
// or absent. An invalid value (e.g. a flag-injection string from a
|
|
303
|
+
// tampered state file) falls back to the integer pr number.
|
|
304
|
+
const safeUrl =
|
|
305
|
+
typeof prUrl === 'string' && /^https:\/\/.+/.test(prUrl) && !prUrl.startsWith('-')
|
|
306
|
+
? prUrl
|
|
307
|
+
: null
|
|
308
|
+
// Need a PR URL to call `gh pr view`; if absent, fall back to the
|
|
309
|
+
// integer pr arg which gh accepts for the active repo.
|
|
310
|
+
const target = safeUrl ?? String(pr)
|
|
311
|
+
let body = null
|
|
312
|
+
if (ghFixture) {
|
|
313
|
+
body = ghFixture
|
|
314
|
+
} else {
|
|
315
|
+
try {
|
|
316
|
+
const res = spawnSync('gh', ['pr', 'view', target, '--json', 'state'], {
|
|
317
|
+
cwd: root,
|
|
318
|
+
encoding: 'utf-8',
|
|
319
|
+
})
|
|
320
|
+
if (res.status === 0 && res.stdout) body = res.stdout
|
|
321
|
+
} catch {
|
|
322
|
+
/* gh transport error — skip this entry */
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (!body) {
|
|
326
|
+
skipped.push({ pr, reason: 'gh-unavailable' })
|
|
327
|
+
continue
|
|
328
|
+
}
|
|
329
|
+
let parsed
|
|
330
|
+
try {
|
|
331
|
+
parsed = JSON.parse(body)
|
|
332
|
+
} catch {
|
|
333
|
+
skipped.push({ pr, reason: 'malformed-gh-json' })
|
|
334
|
+
continue
|
|
335
|
+
}
|
|
336
|
+
const state = parsed?.state ?? null
|
|
337
|
+
if (state !== 'MERGED' && state !== 'CLOSED') {
|
|
338
|
+
skipped.push({ pr, reason: `pr-state-${state ?? 'unknown'}` })
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
341
|
+
if (!dryRun) deleteState(root, pr)
|
|
342
|
+
removed.push({ pr, pr_state: state })
|
|
343
|
+
// Rate-limit ≤5/sec per brief — sleep 200ms between live gh calls.
|
|
344
|
+
// Use async setTimeout (non-blocking) rather than Atomics.wait.
|
|
345
|
+
if (!ghFixture && i < watches.length - 1) {
|
|
346
|
+
await new Promise((resolve) => setTimeout(resolve, 200))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return ok({
|
|
350
|
+
status: 'ok',
|
|
351
|
+
command: 'ci-watch-sweep',
|
|
352
|
+
dry_run: dryRun,
|
|
353
|
+
removed,
|
|
354
|
+
skipped,
|
|
355
|
+
total_watches: watches.length,
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
248
359
|
// ---------------------------------------------------------------------------
|
|
249
360
|
// status
|
|
250
361
|
// ---------------------------------------------------------------------------
|