qfai 2.1.1 → 2.1.2
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/README.md +56 -36
- package/assets/defaults/agent-routing/qfai-triage.yml +9 -0
- package/assets/defaults/workflows/run-owned-cleanup.yml +12 -0
- package/assets/init/.claude/settings.json +27 -9
- package/assets/init/.codex/hooks.json +20 -8
- package/assets/init/.qfai/assistant/agent/frontend-engineer.md +5 -0
- package/assets/init/.qfai/assistant/agent/product-experience-architect.md +4 -0
- package/assets/init/.qfai/assistant/agent/product-surface-reviewer.md +7 -0
- package/assets/init/.qfai/assistant/agent/requirements-analyst.md +8 -0
- package/assets/init/.qfai/assistant/rule/communication.md +18 -8
- package/assets/init/.qfai/assistant/rule/drift-protocol.md +58 -2
- package/assets/init/.qfai/assistant/rule/references/asset-authoring.md +6 -4
- package/assets/init/.qfai/assistant/rule/references/worker-edit-boundary.md +48 -0
- package/assets/init/.qfai/assistant/rule/shared-skill-delegation-baseline.md +15 -25
- package/assets/init/.qfai/assistant/rule/shared-skill-operating-baseline.md +8 -8
- package/assets/init/.qfai/assistant/rule/ui-definition-protocol.md +6 -1
- package/assets/init/.qfai/assistant/skill/qfai-configure/SKILL.md +3 -3
- package/assets/init/.qfai/assistant/skill/qfai-discussion/references/discussion-completion-matrix.md +3 -7
- package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui-bearing-playbook.md +2 -4
- package/assets/init/.qfai/assistant/skill/qfai-discussion/references/ui_ux_best_practices.md +1 -6
- package/assets/init/.qfai/assistant/skill/qfai-discussion/templates/uiux/00_index.md +0 -16
- package/assets/init/.qfai/assistant/skill/qfai-implement/SKILL.md +9 -3
- package/assets/init/.qfai/assistant/skill/qfai-implement/references/parallelization-policy.md +9 -1
- package/assets/init/.qfai/assistant/skill/qfai-implement/references/red-admissibility.md +46 -0
- package/assets/init/.qfai/assistant/skill/qfai-implement/references/ui-affecting.md +3 -0
- package/assets/init/.qfai/assistant/skill/qfai-maintain/SKILL.md +3 -3
- package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/SKILL.md +12 -6
- package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-guide.md +1 -265
- package/assets/init/.qfai/assistant/skill/qfai-migration-v1-to-v2/references/migration-placement.md +273 -0
- package/assets/init/.qfai/assistant/skill/qfai-prototyping/SKILL.md +4 -3
- package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/review-payload-schema.md +2 -3
- package/assets/init/.qfai/assistant/skill/qfai-prototyping/references/reviewer-prompt.md +17 -1
- package/assets/init/.qfai/assistant/skill/qfai-run/SKILL.md +30 -17
- package/assets/init/.qfai/assistant/skill/qfai-run/references/evidence-delivery.md +322 -0
- package/assets/init/.qfai/assistant/skill/qfai-run/references/extraction.md +47 -24
- package/assets/init/.qfai/assistant/skill/qfai-run/references/operator-screens.md +218 -11
- package/assets/init/.qfai/assistant/skill/qfai-run/references/plan.md +4 -3
- package/assets/init/.qfai/assistant/skill/qfai-run/references/stage-points.md +14 -0
- package/assets/init/.qfai/assistant/skill/qfai-sdd/SKILL.md +4 -3
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/contract-artifact-rules.md +1 -0
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md +23 -5
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/requirements-decomposition.md +8 -0
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-execution-playbook.md +29 -4
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/sdd-triage.md +132 -4
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/spec-traceability-rules.md +14 -2
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md +114 -0
- package/assets/init/.qfai/assistant/skill/qfai-sdd/references/ui-design-contract-normalization.md +4 -29
- package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/change-request.md +38 -1
- package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/contracts/ui-contract.sample.yaml +52 -5
- package/assets/init/.qfai/assistant/skill/qfai-sdd/templates/spec/decisions.md +3 -0
- package/assets/init/.qfai/assistant/skill/qfai-triage/SKILL.md +29 -19
- package/assets/init/.qfai/assistant/skill/qfai-verify/SKILL.md +5 -4
- package/assets/init/.qfai/assistant/skill/qfai-verify/references/context-load.md +9 -6
- package/assets/init/.qfai/assistant/skill/qfai-verify/references/verify-output-contract.md +72 -7
- package/assets/init/.qfai/assistant/skill/web-research/SKILL.md +35 -21
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/config.toml +2 -4
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/brave-search/mcp-config.json +9 -5
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/.mcp.json +1 -2
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/config.toml +3 -3
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/firecrawl/mcp-config.json +11 -8
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/config.toml +1 -1
- package/assets/init/.qfai/assistant/skill/web-research/mcp-templates/playwright/mcp-config.json +7 -3
- package/assets/init/.qfai/assistant/step/common-design-md/STEP.md +0 -4
- package/assets/init/.qfai/assistant/step/common-gate-run/STEP.md +98 -8
- package/assets/init/.qfai/assistant/step/common-review-cycle/STEP.md +3 -2
- package/assets/init/.qfai/assistant/step/discussion-pack/STEP.md +1 -0
- package/assets/init/.qfai/assistant/step/discussion-research/STEP.md +2 -0
- package/assets/init/.qfai/assistant/step/discussion-uiux/STEP.md +1 -3
- package/assets/init/.qfai/assistant/step/implement-backport/STEP.md +1 -1
- package/assets/init/.qfai/assistant/step/implement-benchmark/STEP.md +7 -4
- package/assets/init/.qfai/assistant/step/implement-dep-bump/STEP.md +4 -1
- package/assets/init/.qfai/assistant/step/implement-diagnose/STEP.md +2 -0
- package/assets/init/.qfai/assistant/step/implement-oracle-parity/STEP.md +2 -2
- package/assets/init/.qfai/assistant/step/implement-quarantine/STEP.md +3 -3
- package/assets/init/.qfai/assistant/step/implement-refactor/STEP.md +2 -2
- package/assets/init/.qfai/assistant/step/implement-regression-fix/STEP.md +4 -3
- package/assets/init/.qfai/assistant/step/implement-retire/STEP.md +6 -3
- package/assets/init/.qfai/assistant/step/implement-revert/STEP.md +4 -3
- package/assets/init/.qfai/assistant/step/implement-seam/STEP.md +6 -5
- package/assets/init/.qfai/assistant/step/implement-stress-harness/STEP.md +1 -1
- package/assets/init/.qfai/assistant/step/implement-sweep/STEP.md +4 -3
- package/assets/init/.qfai/assistant/step/implement-tdd/STEP.md +39 -5
- package/assets/init/.qfai/assistant/step/implement-tooling/STEP.md +22 -2
- package/assets/init/.qfai/assistant/step/maintain-edit/STEP.md +18 -14
- package/assets/init/.qfai/assistant/step/prototyping-grill/STEP.md +4 -3
- package/assets/init/.qfai/assistant/step/prototyping-loop/STEP.md +4 -0
- package/assets/init/.qfai/assistant/step/prototyping-preflight/STEP.md +1 -3
- package/assets/init/.qfai/assistant/step/sdd-contract/STEP.md +11 -0
- package/assets/init/.qfai/assistant/step/sdd-flow/STEP.md +2 -0
- package/assets/init/.qfai/assistant/step/sdd-story/STEP.md +10 -0
- package/assets/init/.qfai/assistant/step/sdd-triage/STEP.md +30 -8
- package/assets/init/.qfai/assistant/step/triage-answer/STEP.md +5 -4
- package/assets/init/.qfai/assistant/step/triage-close/STEP.md +33 -23
- package/assets/init/.qfai/assistant/step/triage-cluster/STEP.md +5 -3
- package/assets/init/.qfai/assistant/step/triage-decompose/STEP.md +2 -0
- package/assets/init/.qfai/assistant/step/triage-dedupe/STEP.md +4 -0
- package/assets/init/.qfai/assistant/step/triage-handoff/STEP.md +13 -4
- package/assets/init/.qfai/assistant/step/triage-investigate/STEP.md +8 -5
- package/assets/init/.qfai/assistant/step/triage-owned-cleanup/STEP.md +86 -0
- package/assets/init/.qfai/assistant/step/triage-request-info/STEP.md +2 -0
- package/assets/init/.qfai/assistant/step/triage-security-intake/STEP.md +6 -3
- package/assets/init/.qfai/assistant/step/verify-change-note/STEP.md +1 -0
- package/assets/init/.qfai/assistant/step/verify-commit/STEP.md +37 -9
- package/assets/init/.qfai/assistant/step/verify-external/STEP.md +5 -2
- package/assets/init/.qfai/assistant/step/verify-qfai-gate/STEP.md +7 -16
- package/assets/init/.qfai/assistant/step/verify-release-notes/STEP.md +1 -0
- package/assets/init/.qfai/assistant/step/verify-repeat-run/STEP.md +6 -4
- package/assets/init/.qfai/assistant/step/verify-repo-gate/STEP.md +121 -30
- package/assets/init/root/.agents/rules/ai-readable-markdown.md +2 -2
- package/assets/init/root/.agents/rules/api-budget.md +187 -8
- package/assets/init/root/.agents/rules/documentation-clarity.md +162 -33
- package/assets/init/root/.agents/rules/minimal-implementation.md +3 -3
- package/assets/init/root/.agents/rules/reminders.json +27 -14
- package/assets/init/root/.agents/rules/session-feedback.md +136 -0
- package/assets/init/root/.agents/rules/temporary-files.md +33 -0
- package/assets/init/root/.agents/rules/user-questions.md +48 -37
- package/assets/init/root/.github/workflows/qfai-docs.yml +1 -1
- package/assets/init/root/AGENTS.md +23 -22
- package/assets/sandbox-templates/default-deny.yaml +9 -3
- package/assets/schemas/workflow/extraction.schema.json +85 -5
- package/assets/uix-rev/comparison-review.md +1 -1
- package/assets/uix-rev/contracts-review.md +1 -2
- package/dist/cli/index.cjs +6697 -8810
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.mjs +6036 -8152
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.cjs +4947 -7746
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -658
- package/dist/index.d.ts +18 -658
- package/dist/index.mjs +4866 -7640
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -6
- package/assets/init/root/CLAUDE.md +0 -46
package/README.md
CHANGED
|
@@ -79,11 +79,21 @@ Then open your AI coding agent in the repository and describe the change in your
|
|
|
79
79
|
|
|
80
80
|
> Let each customer register up to five notification addresses, with no duplicates.
|
|
81
81
|
|
|
82
|
+
If you only have an idea, say so: the run starts with a discussion that structures scope and open questions.
|
|
83
|
+
|
|
82
84
|
The agent announces the goal, the stages it will run and the files it may change, then runs them.
|
|
83
85
|
It asks you only what it cannot decide for you, such as whether to create a new story.
|
|
86
|
+
Answer its questions, or say `stop` to end the run.
|
|
84
87
|
The stages fill the seeded story tree and follow the project's Standard commands in
|
|
85
88
|
`<paths.contractsDir>/tech.md` (by default `.qfai/spec/03_contract/tech.md`) for its quality gates.
|
|
86
89
|
|
|
90
|
+
When the run ends, check the result:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npx qfai validate
|
|
94
|
+
npx qfai report
|
|
95
|
+
```
|
|
96
|
+
|
|
87
97
|
To drive the stages yourself instead:
|
|
88
98
|
Run `/qfai-discussion` and `/qfai-sdd` to fill the seeded story tree.
|
|
89
99
|
See [Invoking a stage directly](#invoking-a-stage-directly-expert-path).
|
|
@@ -313,27 +323,22 @@ flowchart LR
|
|
|
313
323
|
(`assistant/rule/`, `skill/`, `agent/` and `prompt/`), plus `qfai.config.yaml`.
|
|
314
324
|
- Options:
|
|
315
325
|
|
|
316
|
-
| Flag
|
|
317
|
-
|
|
|
318
|
-
| `--dir <path>`
|
|
319
|
-
| `--root <path>`
|
|
320
|
-
| `--force`
|
|
321
|
-
| `--dry-run`
|
|
322
|
-
| `--
|
|
323
|
-
| `--
|
|
324
|
-
| `--
|
|
325
|
-
| `--
|
|
326
|
-
| `--version`, `-V` | Print the installed QFAI version to stdout and exit 0. Accepted by every command, `init` included, and handled before the command runs, so it works outside a project too. |
|
|
327
|
-
|
|
328
|
-
- `QFAI-DEPRECATED-001` means the legacy assistant layout remains past its
|
|
329
|
-
supported window. Run `npx qfai init --upgrade-assistant-tree` to copy
|
|
330
|
-
recognized files into the current tree without deleting their sources.
|
|
326
|
+
| Flag | Effect |
|
|
327
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
328
|
+
| `--dir <path>` | Output directory (default: the current directory). Wins over `--root` when both are given. |
|
|
329
|
+
| `--root <path>` | Every other command reads this as the target directory; `init` reads it as the output directory too, but only when `--dir` is omitted. |
|
|
330
|
+
| `--force` | Overwrite the shipped skills, steps, agents and rules, the rule masters under `.agents/rules/`, their host wrappers, and generated Copilot instructions, whether or not you edited them. Project content, `qfai.config.yaml`, `DESIGN.md`, `AGENTS.md` and routing overrides are preserved. The managed `.gitignore` block and `core.symlinks` setting are repaired on every non-dry-run. |
|
|
331
|
+
| `--dry-run` | Report what would change and write nothing. |
|
|
332
|
+
| `--yes` | Reserved for a future interactive mode; no behavioural difference today. |
|
|
333
|
+
| `--verbose` | Expand the run report's `skipped` list to the full path listing. Off by default, so a no-op re-run prints the skip count and a pointer to this flag instead of every shipped asset path. It does not gate the written or removed listings: those are printed whenever they have entries, with or without this flag. |
|
|
334
|
+
| `--help`, `-h` | Print the CLI usage banner and exit without writing anything. Accepted by every command, `init` included, and handled before the command runs. |
|
|
335
|
+
| `--version`, `-V` | Print the installed QFAI version to stdout and exit 0. Accepted by every command, `init` included, and handled before the command runs, so it works outside a project too. |
|
|
331
336
|
|
|
332
337
|
- `npx qfai validate`
|
|
333
338
|
- Validates the story tree, contracts and test obligations, writes `.qfai/report/validate.json`,
|
|
334
339
|
and appends run logs to `.qfai/report/run-*/`; use `--fail-on error` (or `--fail-on warning`) to turn it into a CI gate,
|
|
335
340
|
and `--format github` to emit GitHub-friendly annotations.
|
|
336
|
-
Use `--flow BF-0001` to scope a run to one business flow.
|
|
341
|
+
Use `--flow BF-0001` to scope a run to one business flow.
|
|
337
342
|
Use `--profile discussion|sdd|prototyping|atdd|tdd|verify` for local skill-owned checks; CI should use default/full validation (or `verify` / `tdd` for the dedicated CI gates).
|
|
338
343
|
- `npx qfai report`
|
|
339
344
|
- Produces a human-readable report (`report.md` by default) or an internal JSON export (`report.json`) from `validate.json`; use `--base-url` to link file paths in Markdown to your repository viewer.
|
|
@@ -426,15 +431,29 @@ Notes.
|
|
|
426
431
|
issue order are not stable; match on `issues[].code`.
|
|
427
432
|
- `report.json`, `doctor.json`, and `run-*` JSON logs are internal exports and are not a stable external contract; prefer `report.md` for integrations that must survive tool upgrades.
|
|
428
433
|
|
|
429
|
-
##
|
|
434
|
+
## Dependency security
|
|
430
435
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
+
The glob-matching dependency `braces` has an unresolved
|
|
437
|
+
[stack-exhaustion denial-of-service advisory](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm).
|
|
438
|
+
QFAI accepts this residual risk only for reviewed project patterns and paths,
|
|
439
|
+
trusted configuration and trusted SDK callers. This is risk acceptance, not a
|
|
440
|
+
clean audit or proof that QFAI is unaffected. Reproduction and exploitation in
|
|
441
|
+
QFAI remain unverified.
|
|
436
442
|
|
|
437
|
-
|
|
443
|
+
Review include, ignore and negative patterns, constructed paths and SDK inputs.
|
|
444
|
+
`validateProject` accepts a caller-supplied `ConfigLoadResult` without invoking
|
|
445
|
+
the configuration loader; the caller must review those values too. Catching
|
|
446
|
+
errors and limiting results after glob expansion do not defend against stack
|
|
447
|
+
exhaustion during expansion.
|
|
448
|
+
|
|
449
|
+
Untrusted pull requests, externally supplied configuration and service request
|
|
450
|
+
inputs are outside this acceptance. Review those inputs and restrict access;
|
|
451
|
+
do not expose glob processing to them until your mitigation and security review
|
|
452
|
+
cover them.
|
|
453
|
+
|
|
454
|
+
Reassess the risk when a compatible patch becomes available or untrusted input
|
|
455
|
+
is needed. A forced downgrade to `qfai@0.4.0` is not a compatible fix for the
|
|
456
|
+
current major API; do not apply it through `npm audit fix --force`.
|
|
438
457
|
|
|
439
458
|
## FAQ
|
|
440
459
|
|
|
@@ -607,6 +626,17 @@ Waiver policy.
|
|
|
607
626
|
- Expired waivers are reported as warnings (`QFAI-WAIVER-003`) and must be renewed or removed with evidence.
|
|
608
627
|
- Suppressed findings remain visible in reports as `suppressed=true`; waivers do not erase findings.
|
|
609
628
|
|
|
629
|
+
When upgrading a project with `.qfai/waivers.yml`, back up that file. Check the
|
|
630
|
+
current public finding codes and the release notes for confirmed removals or
|
|
631
|
+
renames. Update a `rule:` value only when the documented rename preserves the
|
|
632
|
+
finding's meaning and scope. Remove confirmed retired or inapplicable waivers,
|
|
633
|
+
keeping the backup and recording each removal's reason for the operator. Do not
|
|
634
|
+
rename whole code prefixes or automatically delete an unknown rule.
|
|
635
|
+
|
|
636
|
+
Rerun full validation and require zero errors. An unknown waiver rule produces
|
|
637
|
+
`QFAI-WAIVER-004` at `warning` and is not applied in that run; investigate it
|
|
638
|
+
against the current diagnostics and release notes.
|
|
639
|
+
|
|
610
640
|
Typical customizations.
|
|
611
641
|
|
|
612
642
|
- Add a `doctor` step before validate if you want to fail fast on path/glob/config issues.
|
|
@@ -929,7 +959,6 @@ commit that bumps the package, and to keep the two from being merged separately.
|
|
|
929
959
|
│ │ └── open-questions.md
|
|
930
960
|
│ └── waivers.yml
|
|
931
961
|
├── AGENTS.md
|
|
932
|
-
├── CLAUDE.md
|
|
933
962
|
└── qfai.config.yaml
|
|
934
963
|
```
|
|
935
964
|
|
|
@@ -966,17 +995,6 @@ provide those defaults to the agent.
|
|
|
966
995
|
The `.claude` / `.github` agent wrappers are symlinks and follow the canonical document
|
|
967
996
|
automatically; the Codex profiles are generated files, so rerun `npx qfai init --force`
|
|
968
997
|
to refresh them (and any other wrapper asset that has drifted).
|
|
969
|
-
`--force` deletes as well as overwrites: it removes the command and prompt wrappers
|
|
970
|
-
under `.claude/commands/` and `.github/prompts/`, and the skill wrappers under
|
|
971
|
-
`.claude/skills/`, `.agents/skills/`, `.codex/skills/` and `.github/skills/` for skills that are
|
|
972
|
-
not in the current release, whether those wrappers are directories or symlinks.
|
|
973
|
-
Files it did not write — a project's own slash command, prompt file or skill,
|
|
974
|
-
including one published from a project-authored `.qfai/assistant/skill/` entry — are left in place,
|
|
975
|
-
whatever they are named. Ownership is read from the file, not the name: a wrapper is removed only when
|
|
976
|
-
it carries the delegation line to the canonical document of the same name, or is a symlink into
|
|
977
|
-
`.qfai/assistant/skill/`. One consequence of that: a symlink has no content to prove who wrote it, so
|
|
978
|
-
if you publish a canonical skill of your own under a name QFAI itself once shipped, `--force` removes
|
|
979
|
-
that link. Your `.qfai/assistant/skill/` entry is untouched; re-create the link to publish it again.
|
|
980
998
|
|
|
981
999
|
### Supported hosts
|
|
982
1000
|
|
|
@@ -986,7 +1004,9 @@ No host is declared supported in this release.
|
|
|
986
1004
|
### Cross-AI rules and the writing reminder
|
|
987
1005
|
|
|
988
1006
|
`npx qfai init` seeds `.agents/rules/**`, the rule set every AI coding agent in
|
|
989
|
-
the project shares, and points `AGENTS.md`
|
|
1007
|
+
the project shares, and points `AGENTS.md` at it, with a line for each rule that says when
|
|
1008
|
+
to read it. Claude Code reads `AGENTS.md` when the project has no `CLAUDE.md` (Claude Code
|
|
1009
|
+
2.1.277 or later). One of those
|
|
990
1010
|
rules, `documentation-clarity.md`, sets the writing standard for pull requests,
|
|
991
1011
|
issues, code comments and Markdown: no project-local identifiers, no account of
|
|
992
1012
|
how the work went, cut to what a reader outside the team can follow.
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
routing:
|
|
5
5
|
# The triage steps change no tracked file, so each has one author and no
|
|
6
6
|
# build or test phase.
|
|
7
|
+
- step: triage-owned-cleanup
|
|
8
|
+
phases:
|
|
9
|
+
- id: cleanup
|
|
10
|
+
mandatory_agents: [devops-ci-engineer]
|
|
11
|
+
conditional_agents: []
|
|
12
|
+
parallel_groups: []
|
|
13
|
+
rerun_policy: changed-scope-dependents
|
|
14
|
+
review_profile: default
|
|
15
|
+
|
|
7
16
|
- step: triage-close
|
|
8
17
|
phases:
|
|
9
18
|
- id: record
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"command": "node",
|
|
31
31
|
"args": [
|
|
32
32
|
"-e",
|
|
33
|
-
"try{let
|
|
33
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}const p=i.prompt;if(!(typeof p==='string'&&/^[ \\t]*(<(task-notification|wake)[\\s>]|\\[SYSTEM NOTIFICATION)/m.test(p))){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
|
|
34
34
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
35
35
|
"structured-question"
|
|
36
36
|
]
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"command": "node",
|
|
47
47
|
"args": [
|
|
48
48
|
"-e",
|
|
49
|
-
"try{let
|
|
49
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}const p=i.prompt;if(!(typeof p==='string'&&/^[ \\t]*(<(task-notification|wake)[\\s>]|\\[SYSTEM NOTIFICATION)/m.test(p))){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
|
|
50
50
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
51
51
|
"free-text-entry"
|
|
52
52
|
]
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"command": "node",
|
|
97
97
|
"args": [
|
|
98
98
|
"-e",
|
|
99
|
-
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
99
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
|
|
100
100
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
101
101
|
"grilling-design-artifact"
|
|
102
102
|
]
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"command": "node",
|
|
113
113
|
"args": [
|
|
114
114
|
"-e",
|
|
115
|
-
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
115
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
|
|
116
116
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
117
117
|
"grilling-delegation"
|
|
118
118
|
]
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"command": "node",
|
|
129
129
|
"args": [
|
|
130
130
|
"-e",
|
|
131
|
-
"try{const
|
|
131
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
|
|
132
132
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
133
133
|
"grilling-plan"
|
|
134
134
|
]
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"command": "node",
|
|
145
145
|
"args": [
|
|
146
146
|
"-e",
|
|
147
|
-
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}const c=i.tool_input&&i.tool_input.command;if(typeof c==='string'&&/(^|[^\\w.-])gh([^\\w.-]|$)|api\\.github\\.com/.test(c)){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
147
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}const c=i.tool_input&&i.tool_input.command;if(typeof c==='string'&&/(^|[^\\w.-])gh([^\\w.-]|$)|api\\.github\\.com/.test(c)){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
|
|
148
148
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
149
149
|
"api-budget"
|
|
150
150
|
]
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
"command": "node",
|
|
164
164
|
"args": [
|
|
165
165
|
"-e",
|
|
166
|
-
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
166
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
|
|
167
167
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
168
168
|
"documentation-clarity-after-write"
|
|
169
169
|
]
|
|
@@ -175,7 +175,7 @@
|
|
|
175
175
|
"command": "node",
|
|
176
176
|
"args": [
|
|
177
177
|
"-e",
|
|
178
|
-
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
178
|
+
"try{const f=require('fs');let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}catch{}",
|
|
179
179
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
180
180
|
"documentation-clarity-after-edit"
|
|
181
181
|
]
|
|
@@ -191,13 +191,31 @@
|
|
|
191
191
|
"command": "node",
|
|
192
192
|
"args": [
|
|
193
193
|
"-e",
|
|
194
|
-
"try{const f=require('fs'),p=require('path'),R=p.dirname(p.dirname(p.dirname(process.argv[1])));let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}const u=i.tool_input&&i.tool_input.file_path;let src=true;if(typeof u==='string'&&(x=>{const s=x.split(p.sep),b=s.pop();return p.isAbsolute(x)||s[0]==='..'||s.some(y=>['tmp','test','tests','__tests__','node_modules','.qfai','.agents'].includes(y))||b[0]==='.'||/\\.(test|spec)\\./.test(b)||['.md','.mdx','.markdown','.txt','.rst','.yml','.yaml','.json','.jsonc','.json5','.toml','.ini','.csv','.tsv','.lock','.svg','.png','.jpg','.jpeg','.gif'].includes(p.extname(b).toLowerCase())})(p.relative(R,p.resolve(R,u))))src=false;if(src){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
194
|
+
"try{const f=require('fs'),p=require('path'),R=p.dirname(p.dirname(p.dirname(process.argv[1])));let i={};try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{}const u=i.tool_input&&i.tool_input.file_path;let src=true;if(typeof u==='string'&&(x=>{const s=x.split(p.sep),b=s.pop();return p.isAbsolute(x)||s[0]==='..'||s.some(y=>['tmp','test','tests','__tests__','node_modules','.qfai','.agents'].includes(y))||b[0]==='.'||/\\.(test|spec)\\./.test(b)||['.md','.mdx','.markdown','.txt','.rst','.yml','.yaml','.json','.jsonc','.json5','.toml','.ini','.csv','.tsv','.lock','.svg','.png','.jpg','.jpeg','.gif'].includes(p.extname(b).toLowerCase())})(p.relative(R,p.resolve(R,u))))src=false;if(src){let n=1;if(typeof i.session_id==='string'){try{const t=require('path').join(require('os').tmpdir(),['qfai-reminder',i.session_id,i.agent_id||'main',process.argv[2]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(e!==undefined){const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}",
|
|
195
195
|
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
196
196
|
"minimal-implementation"
|
|
197
197
|
]
|
|
198
198
|
}
|
|
199
199
|
]
|
|
200
200
|
}
|
|
201
|
+
],
|
|
202
|
+
"Stop": [
|
|
203
|
+
{
|
|
204
|
+
"matcher": "*",
|
|
205
|
+
"hooks": [
|
|
206
|
+
{
|
|
207
|
+
"type": "command",
|
|
208
|
+
"statusMessage": "QFAI session feedback reminder",
|
|
209
|
+
"command": "node",
|
|
210
|
+
"args": [
|
|
211
|
+
"-e",
|
|
212
|
+
"try{const f=require('fs');let i;try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{process.exit(0)}const l=typeof i.last_assistant_message==='string'?i.last_assistant_message.trim():'';if(i.stop_hook_active===true||(l.length>0&&('?'+String.fromCharCode(65311)).includes(l.slice(-1))))process.exit(0);const m=JSON.parse(f.readFileSync(process.argv[1],'utf8'))[process.argv[2]];if(m!==undefined)console.log(JSON.stringify(m))}catch{}",
|
|
213
|
+
"${CLAUDE_PROJECT_DIR}/.agents/rules/reminders.json",
|
|
214
|
+
"session-feedback"
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
201
219
|
]
|
|
202
220
|
}
|
|
203
221
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
{
|
|
7
7
|
"type": "command",
|
|
8
8
|
"statusMessage": "QFAI structured-question reminder",
|
|
9
|
-
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const
|
|
9
|
+
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" structured-question",
|
|
10
10
|
"timeout": 10
|
|
11
11
|
}
|
|
12
12
|
]
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
{
|
|
17
17
|
"type": "command",
|
|
18
18
|
"statusMessage": "QFAI free-text entry reminder",
|
|
19
|
-
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){const
|
|
19
|
+
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" free-text-entry",
|
|
20
20
|
"timeout": 10
|
|
21
21
|
}
|
|
22
22
|
]
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
{
|
|
51
51
|
"type": "command",
|
|
52
52
|
"statusMessage": "QFAI grilling reminder: design artifact",
|
|
53
|
-
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
53
|
+
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" grilling-design-artifact",
|
|
54
54
|
"timeout": 10
|
|
55
55
|
}
|
|
56
56
|
]
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
{
|
|
62
62
|
"type": "command",
|
|
63
63
|
"statusMessage": "QFAI grilling reminder: delegation",
|
|
64
|
-
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
64
|
+
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))||{}}catch{}let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}catch{}\" grilling-delegation",
|
|
65
65
|
"timeout": 10
|
|
66
66
|
}
|
|
67
67
|
]
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
{
|
|
73
73
|
"type": "command",
|
|
74
74
|
"statusMessage": "QFAI api-budget reminder",
|
|
75
|
-
"command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/[^\\w.-]gh[^\\w.-]|api\\.github\\.com/.test(' '+i+' ')){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
75
|
+
"command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/[^\\w.-]gh[^\\w.-]|api\\.github\\.com/.test(' '+i+' ')){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" api-budget",
|
|
76
76
|
"timeout": 10
|
|
77
77
|
}
|
|
78
78
|
]
|
|
@@ -85,13 +85,13 @@
|
|
|
85
85
|
{
|
|
86
86
|
"type": "command",
|
|
87
87
|
"statusMessage": "QFAI documentation-clarity reminder",
|
|
88
|
-
"command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Add File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
88
|
+
"command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Add File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" documentation-clarity-after-write",
|
|
89
89
|
"timeout": 10
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"type": "command",
|
|
93
93
|
"statusMessage": "QFAI documentation-clarity reminder",
|
|
94
|
-
"command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Update File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
94
|
+
"command": "node -e \"try{const j=JSON.parse(require('fs').readFileSync(0,'utf8')),i=j.tool_input.command;if(typeof i==='string'&&/^\\*\\*\\* Update File: .*\\.md\\r?\\n/m.test(i)){const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" documentation-clarity-after-edit",
|
|
95
95
|
"timeout": 10
|
|
96
96
|
}
|
|
97
97
|
]
|
|
@@ -102,7 +102,19 @@
|
|
|
102
102
|
{
|
|
103
103
|
"type": "command",
|
|
104
104
|
"statusMessage": "QFAI minimal-implementation reminder",
|
|
105
|
-
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let go=true,j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))}catch{}try{const R=p.dirname(p.dirname(p.dirname(r))),n=(x=>{const s=x.split(p.sep),b=s.pop();return p.isAbsolute(x)||s[0]==='..'||s.some(y=>['tmp','test','tests','__tests__','node_modules','.qfai','.agents'].includes(y))||b[0]==='.'||/\\.(test|spec)\\./.test(b)||['.md','.mdx','.markdown','.txt','.rst','.yml','.yaml','.json','.jsonc','.json5','.toml','.ini','.csv','.tsv','.lock','.svg','.png','.jpg','.jpeg','.gif'].includes(p.extname(b).toLowerCase())}),a=[...j.tool_input.command.matchAll(/^\\*\\*\\* (?:(?:Add|Update|Delete) File|Move to): (.+)/gm)].map(m=>p.relative(R,p.resolve(process.cwd(),m[1].trim())));if(a.length>0&&a.every(n))go=false}catch{}if(go){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}
|
|
105
|
+
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let go=true,j={};try{j=JSON.parse(f.readFileSync(0,'utf8'))}catch{}try{const R=p.dirname(p.dirname(p.dirname(r))),n=(x=>{const s=x.split(p.sep),b=s.pop();return p.isAbsolute(x)||s[0]==='..'||s.some(y=>['tmp','test','tests','__tests__','node_modules','.qfai','.agents'].includes(y))||b[0]==='.'||/\\.(test|spec)\\./.test(b)||['.md','.mdx','.markdown','.txt','.rst','.yml','.yaml','.json','.jsonc','.json5','.toml','.ini','.csv','.tsv','.lock','.svg','.png','.jpg','.jpeg','.gif'].includes(p.extname(b).toLowerCase())}),a=[...j.tool_input.command.matchAll(/^\\*\\*\\* (?:(?:Add|Update|Delete) File|Move to): (.+)/gm)].map(m=>p.relative(R,p.resolve(process.cwd(),m[1].trim())));if(a.length>0&&a.every(n))go=false}catch{}if(go){let n=1;if(typeof j.session_id==='string'){try{const t=p.join(require('os').tmpdir(),['qfai-reminder',j.session_id,j.agent_id||'main',process.argv[1]].join('-').replace(/[^\\w.-]/g,'_'));f.appendFileSync(t,'.');n=f.statSync(t).size}catch{}}const e=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];if(e===undefined){}else{const {briefContext,...m}=e;if(n>1&&typeof briefContext==='string')m.hookSpecificOutput.additionalContext=briefContext;console.log(JSON.stringify(m))}}}}catch{}\" minimal-implementation",
|
|
106
|
+
"timeout": 10
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"Stop": [
|
|
112
|
+
{
|
|
113
|
+
"hooks": [
|
|
114
|
+
{
|
|
115
|
+
"type": "command",
|
|
116
|
+
"statusMessage": "QFAI session feedback reminder",
|
|
117
|
+
"command": "node -e \"try{const f=require('fs'),p=require('path');let d=process.cwd(),r;for(;;){const c=p.join(d,'.agents','rules','reminders.json');if(r===undefined&&f.existsSync(c))r=c;if(f.existsSync(p.join(d,'.git')))break;const u=p.dirname(d);if(u===d){r=undefined;break}d=u}if(r){let i;try{i=JSON.parse(f.readFileSync(0,'utf8'))}catch{process.exit(0)}const l=typeof i.last_assistant_message==='string'?i.last_assistant_message.trim():'';if(i.stop_hook_active===true||(l.length>0&&('?'+String.fromCharCode(65311)).includes(l.slice(-1))))process.exit(0);const m=JSON.parse(f.readFileSync(r,'utf8'))[process.argv[1]];m===undefined||console.log(JSON.stringify(m))}}catch{}\" session-feedback",
|
|
106
118
|
"timeout": 10
|
|
107
119
|
}
|
|
108
120
|
]
|
|
@@ -45,6 +45,11 @@ specialization_tags:
|
|
|
45
45
|
a deliberate shortcut with its ceiling and the condition that lifts it.
|
|
46
46
|
- Apply `.qfai/assistant/rule/ui-procurement.md`: install a component before writing one, and for anything the prototype does not show, take the adopted system's default rather than inventing one.
|
|
47
47
|
- Apply `.agents/rules/interface-clarity.md`: label a control in the user's words, never the parameter's. The label stays; the sentence explaining it goes.
|
|
48
|
+
- Read each screen's `supplements` and `structure` in its UI contract before adding any text.
|
|
49
|
+
Show the title, the group headings, the labels and the declared supplements, and no other text.
|
|
50
|
+
A text the screen needs that the contract lacks, or a declared one the screen should not show,
|
|
51
|
+
is a change to the contract and goes back to `/qfai-sdd`.
|
|
52
|
+
Element ids stay the same when only wording changes.
|
|
48
53
|
|
|
49
54
|
## Inputs you must read
|
|
50
55
|
|
|
@@ -51,6 +51,10 @@ specialization_tags:
|
|
|
51
51
|
- Apply `.agents/rules/minimal-implementation.md`: the first rung is this stage's — whether the thing needs to exist. After a spec row is agreed, that question is a Change Request.
|
|
52
52
|
- Apply `.qfai/assistant/rule/ui-procurement.md`: the theme is adopted from a published one, and a screen's composition comes from a catalogue before it is drawn.
|
|
53
53
|
- Apply `.agents/rules/interface-clarity.md`: a screen that needs explaining is a screen that needs redesigning. Hint text only where a need was demonstrated and the control was improved first.
|
|
54
|
+
- Record the decisions that rule leads to in the screen contract: the `supplements` a screen shows,
|
|
55
|
+
each with the reason it is needed, and the `structure` of any heading beyond the title
|
|
56
|
+
(`.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md`).
|
|
57
|
+
Fix the control before adding a supplement for it.
|
|
54
58
|
|
|
55
59
|
## Inputs you must read
|
|
56
60
|
|
|
@@ -61,6 +61,13 @@ specialization_tags:
|
|
|
61
61
|
- Require more work only on what `.qfai/assistant/rule/shared-skill-delegation-baseline.md#what-a-reviewer-may-demand-more-of-must` admits, and report any other gap as advisory.
|
|
62
62
|
- Apply `.qfai/assistant/rule/ui-procurement.md`: report a region that was drawn where a catalogue had it, and name the item that would have served.
|
|
63
63
|
- Apply `.agents/rules/interface-clarity.md`: report copy that explains the interface as a finding against the control under it, and name the control to fix.
|
|
64
|
+
- Compare the rendered text with each screen's `supplements` and `structure` in its UI contract.
|
|
65
|
+
A text no contract key declares is excess; a declared supplement that is not shown is an unmet need.
|
|
66
|
+
Name the screen, the task or state, the duplicated meaning or unmet need, and the text to remove or the control or structure to repair.
|
|
67
|
+
Apply `.qfai/assistant/skill/qfai-sdd/references/design-anti-patterns.md#copy-that-adds-no-meaning`.
|
|
68
|
+
Never cut a condition, cost, destination, result, recovery step, consequence, label, accessible name, role or state,
|
|
69
|
+
and never move one into a tooltip or placeholder.
|
|
70
|
+
Word counts are observations, never a verdict.
|
|
64
71
|
|
|
65
72
|
## Inputs you must read
|
|
66
73
|
|
|
@@ -64,6 +64,14 @@ specialization_tags:
|
|
|
64
64
|
`/qfai-prototyping` at the validate lane, under `QFAI-AUD-001`. The shipped
|
|
65
65
|
`ui-contract.sample.yaml` carries filled entries; replace them with the
|
|
66
66
|
screen's own rather than emptying the list.
|
|
67
|
+
- For UI-bearing surfaces: every screen also declares `supplements` (`[]` when it
|
|
68
|
+
shows no text beyond its title, group headings and labels), and `structure` when
|
|
69
|
+
it shows a heading beyond its title. Check each proposed text element against the
|
|
70
|
+
screen's tasks and the information the user needs there, and write the ones that
|
|
71
|
+
stay into the contract with the reason. A text that only repeats a heading, an
|
|
72
|
+
action or a condition the user already has is left out. Discussion sidecars
|
|
73
|
+
inform these keys; they do not replace them. See
|
|
74
|
+
`.qfai/assistant/skill/qfai-sdd/references/ui-contract-guide.md`.
|
|
67
75
|
|
|
68
76
|
## Stop conditions
|
|
69
77
|
|
|
@@ -45,14 +45,24 @@ Applies to every text an agent produces: pull request and issue titles and
|
|
|
45
45
|
bodies, source-code comments, and Markdown files — inside the current change
|
|
46
46
|
only, never beyond its diff.
|
|
47
47
|
|
|
48
|
-
-
|
|
49
|
-
project understands, anywhere in source code or Markdown files. Those
|
|
50
|
-
in the pull request, the commit message and the changelog.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
48
|
+
- Keep out issue or pull-request numbers, ticket identifiers and names only
|
|
49
|
+
this project understands, anywhere in source code or Markdown files. Those
|
|
50
|
+
belong in the pull request, the commit message and the changelog.
|
|
51
|
+
Pull request and issue bodies may contain numbers and links.
|
|
52
|
+
- Keep out any account of how the work went. State the current behaviour and
|
|
53
|
+
why it is that way; the history is already in the git log and the pull
|
|
54
|
+
request.
|
|
55
|
+
- Settle the message, then open with the conclusion: the title or opening says
|
|
56
|
+
the result, with no preamble.
|
|
57
|
+
- Cut what is self-evident, repeated or wordy, and announcements, recaps and
|
|
58
|
+
empty intensifiers.
|
|
59
|
+
- Use ordinary vocabulary, one claim per sentence, and name who acts; software
|
|
60
|
+
does not know or decide.
|
|
61
|
+
- Mark what is unverified, and say what a claim rests on.
|
|
62
|
+
- Use bullet lists and tables for parallel items, with lines short enough to
|
|
63
|
+
scan.
|
|
64
|
+
- Open a report with the outcome, and separate what was checked from what was
|
|
65
|
+
not.
|
|
56
66
|
- Re-read every changed line afterwards. Text that reads as a literal
|
|
57
67
|
translation is a defect in any language pair — rewrite it the way someone
|
|
58
68
|
writing natively in that language would.
|
|
@@ -11,7 +11,18 @@ open-questions.md, and every file under paths.contractsDir. Production code and
|
|
|
11
11
|
obligation may change in a later task, but the change must preserve or deliberately reapprove that obligation.
|
|
12
12
|
QFAI-owned files under .qfai/assistant/rule/ are updated through the package, then synchronized by init.
|
|
13
13
|
|
|
14
|
-
A new change-request row in decisions.md is permitted without an earlier change request.
|
|
14
|
+
A new change-request row in decisions.md is permitted without an earlier change request.
|
|
15
|
+
Existing rows are append-only records: a prior row may change its Status cell,
|
|
16
|
+
while its ID, Content, and Approach stay fixed. The sole ID-change exception is
|
|
17
|
+
explicit local renumbering of a unique branch-added decision absent from the
|
|
18
|
+
unique common ancestor of HEAD and a required local base. That operation changes
|
|
19
|
+
only its ID and provably branch-added exact references. Inherited row cells and
|
|
20
|
+
references stay fixed. A changed obligation needs a new row, even when an older
|
|
21
|
+
row discusses the same topic.
|
|
22
|
+
|
|
23
|
+
The sole Approach-change exception is the published decision prefix recovery
|
|
24
|
+
below. It preserves the target's ID, Content, Status and complete original
|
|
25
|
+
Approach suffix. It excludes inherited rows and open-question rows.
|
|
15
26
|
|
|
16
27
|
## Allowed exceptions (minimal whitelist)
|
|
17
28
|
|
|
@@ -25,6 +36,51 @@ diff changes no file under `01_policy/`, `02_business-flow/` or
|
|
|
25
36
|
paths.contractsDir appends no `Change request:` row. A row naming a story file
|
|
26
37
|
the bugfix did not touch would state an upstream change that did not happen.
|
|
27
38
|
|
|
39
|
+
## Published decision prefix recovery
|
|
40
|
+
|
|
41
|
+
The SDD owner may prepend only an approved Evidence/Grounds prefix to a
|
|
42
|
+
published malformed decision added by the branch. Approval of this protocol
|
|
43
|
+
does not authorize any target. First obtain a separate new in-force recovery
|
|
44
|
+
change request naming the target and its repository-relative file.
|
|
45
|
+
|
|
46
|
+
1. Fix the explicit base commit and its unique common ancestor with current
|
|
47
|
+
HEAD. The target ID must be absent from both. The documented published
|
|
48
|
+
source commit must be an ancestor of current HEAD and expose the unique
|
|
49
|
+
original row. Inherited records and open-question rows are excluded.
|
|
50
|
+
2. Read the original raw cell bytes. The row must be single-line valid UTF-8,
|
|
51
|
+
with exactly one ASCII padding space at each Approach delimiter and no
|
|
52
|
+
additional boundary whitespace. Hash the stored Approach bytes with
|
|
53
|
+
SHA-256, excluding only the delimiter pipes and those two padding spaces.
|
|
54
|
+
Do not decode escapes, trim text or normalize it. Ambiguous boundaries,
|
|
55
|
+
additional padding or invalid UTF-8 stop recovery.
|
|
56
|
+
3. Confirm the original Residual risk and Rollback items already satisfy the
|
|
57
|
+
required ordered, nonempty form. The new Approach must be exactly
|
|
58
|
+
`- Evidence: <actual file references> - Grounds: `, including its trailing
|
|
59
|
+
space, followed by the complete original Approach as a byte-identical
|
|
60
|
+
suffix. Preserve the original wording, order, facts, authority and scope.
|
|
61
|
+
The prefixed row must satisfy the existing form checks.
|
|
62
|
+
4. Record the target ID, published source commit, fixed base and common
|
|
63
|
+
ancestor, old and new Approach digests, exact prefix, actual file evidence,
|
|
64
|
+
approval authority, recovery reason, residual risk and rollback in the new
|
|
65
|
+
request. The old digest must match the published source Approach; the new
|
|
66
|
+
digest must match the exact prefix plus that source value. Record actual
|
|
67
|
+
authority; do not invent a user answer or approval.
|
|
68
|
+
5. Immediately before writing, recheck the unique current target, its
|
|
69
|
+
byte-identical ID, Content and Status, and its recorded old Approach digest.
|
|
70
|
+
Unknown or unavailable provenance, inherited or duplicate targets, stale
|
|
71
|
+
data or a change beyond the approved prefix stops recovery before writing.
|
|
72
|
+
Apply only that prefix. Keep every other row byte-identical and run the existing
|
|
73
|
+
validation without an exemption.
|
|
74
|
+
6. Rollback removes only the recorded prefix when the current new Approach
|
|
75
|
+
digest still matches. Otherwise preserve the current row and stop. Removal
|
|
76
|
+
restores the original Approach and its validation failure. Do not reset,
|
|
77
|
+
amend or force push.
|
|
78
|
+
|
|
79
|
+
Status changes, supersession and a diagnostic's rewrite suggestion supply no
|
|
80
|
+
recovery authority and clear no form error. Inherited malformed records need a
|
|
81
|
+
separate owning change request; this operation cannot repair them. These are
|
|
82
|
+
owner instructions, not an automatic recovery command or a concurrency guarantee.
|
|
83
|
+
|
|
28
84
|
## Drift classes
|
|
29
85
|
|
|
30
86
|
Intent drift means an approved obligation should change. Record the current obligation, the proposed behavior, realistic
|
|
@@ -112,6 +168,6 @@ copy of a locked file before deciding its lock is wrong.
|
|
|
112
168
|
## Non-negotiable constraints
|
|
113
169
|
|
|
114
170
|
- Downstream stages do not patch protected upstream artifacts before an in-force Change request: decision row authorizes the path. The drift profile compares the branch against baseBranch and reports QFAI-DRIFT-001 for an unapproved protected change. The SDD owner may create the request row itself without a prior row.
|
|
115
|
-
- Existing decisions.md and open-questions.md rows retain their ID, Content, and Approach. A former row may change Status; new content is appended as a new row.
|
|
171
|
+
- Existing decisions.md and open-questions.md rows retain their ID, Content, and Approach, except for the explicit branch-added decision renumbering and published decision prefix recovery above. Inherited rows retain those cells without exception. A former row may change Status; new content is appended as a new row.
|
|
116
172
|
- Vendored assistant rules are changed in the package and synchronized into projects by `npx qfai init --force`, which overwrites a local edit. A project rule lives in a `*.local.md` overlay beside the vendored one.
|
|
117
173
|
- When approval is unavailable, keep the affected items stopped and report the decision needed. Continue unrelated work.
|