Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .markdownlint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"default": true,
"MD033": false,
"MD060": false,
"MD013": false
}
2 changes: 1 addition & 1 deletion adapters/selfhosted/xwiki.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def page_exists(self, page_name: str) -> bool:

def create_or_update_page(self, page_name: str, title: str, content: str) -> None:
"""Idempotent: creates the page if absent, updates content if present."""
payload = {"title": title, "content": content, "syntax": "markdown/1.2"}
payload = {"title": title, "content": content, "syntax": "xwiki/2.1"}
self._put(self._page_url(page_name), payload)

def deploy(self, rendered_pages: dict[str, tuple[str, str]]) -> None:
Expand Down
2 changes: 1 addition & 1 deletion cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def deploy(target: str, config_path: Path, dry_run: bool) -> None:
for doc in config.documents:
if doc.template is None:
continue
template_file = f"{doc.template}.md.j2"
template_file = f"{doc.template}.xwiki.j2"
content = render_template(
template_name=template_file,
config=config,
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Running `deploy` twice produces the same result without duplicates.

### CLI layer

Single entry point (`python -m cli deploy --target <t> --config <f>`).
Single entry point (`qms-kit deploy --target <t> --config <f>`).
Orchestrates: load config → merge overlay → render templates → call adapter.

## Self-hosted Stack
Expand Down
178 changes: 144 additions & 34 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,159 @@
# Configuration Reference

> TODO: Full reference will be written once the core config loader (Pydantic schema) is complete.
Configuration is split into two layers that are deep-merged at runtime (overlay wins on conflict).

## Overview

Configuration is split into two layers:
## Files

| File | Purpose |
|------|---------|
| `config/core.yaml` | Tool-neutral QMS substance — clauses, documents, roles, records, KPIs |
| `config/<target>.yaml` | Tool overlay template — copy and fill per client |
| `config/clients/<client>.yaml` | Per-client filled overlay (gitignored, not in this repo) |
| `config/core.yaml` | Tool-neutral QMS substance — clauses, documents, roles, records, KPIs, CAPA states |
| `config/selfhosted.yaml` | Self-hosted overlay template (copy per client) |
| `config/clients/<client>.yaml` | Per-client filled overlay (gitignored — never committed) |
| `config/clients/example.yaml` | Reference template showing all available keys |

## Core config keys (`config/core.yaml`)

These keys define the QMS substance and are shared across all targets.

### `meta`

| Key | Type | Description |
|-----|------|-------------|
| `meta.version` | string | Config schema version |
| `meta.standard` | string | QMS standard (e.g. `ISO 9001:2015`) |

### `organisation`

| Key | Type | Description |
|-----|------|-------------|
| `organisation.name` | string | Full legal name of the client organisation |
| `organisation.short` | string | Short identifier used in document IDs |
| `organisation.quality_officer` | string | Full name of the QMO |
| `organisation.management` | string | Full name of the approving manager |

### `clauses`

List of ISO 9001 clause objects (4–10). Each clause has:

| Key | Type | Description |
|-----|------|-------------|
| `id` | string | Clause number (e.g. `"4"`, `"7.5"`) |
| `title` | string | Clause title |
| `documents` | list[string] | Document IDs that belong to this clause |

### `documents`

List of document definitions. Each document has:

| Key | Type | Description |
|-----|------|-------------|
| `id` | string | Unique document ID (e.g. `quality-policy`) |
| `title` | string | Human-readable title |
| `template` | string or null | Template filename without `.md.j2` extension; null = no template |
| `clause` | string | Parent clause ID |

### `record_types`

List of record type definitions (NC, CAPA, Internal Audit, KPI). Each has:

| Key | Type | Description |
|-----|------|-------------|
| `id` | string | Record type ID (e.g. `nc`, `capa`) |
| `label` | string | Human-readable label |
| `clause` | string | ISO clause this record type belongs to |
| `fields` | list[string] | Custom fields required on this record |

### `kpis`

List of KPI definitions. Each has:

| Key | Type | Description |
|-----|------|-------------|
| `id` | string | KPI identifier |
| `label` | string | Human-readable name |
| `unit` | string | Measurement unit (e.g. `%`, `days`) |
| `target` | string | Target value |
| `frequency` | string | Measurement frequency |
| `clause` | string | ISO clause |

### `capa_states`

CAPA state machine definition. Each state has:

| Key | Type | Description |
|-----|------|-------------|
| `id` | string | State identifier |
| `label` | string | Display name |
| `transitions` | list[string] | Valid next state IDs |

---

## Overlay keys

### Common

| Key | Type | Description |
|-----|------|-------------|
| `target` | string | `selfhosted` / `atlassian` / `m365` |
| `organisation.*` | — | Overrides any `organisation` key from core |

### Self-hosted (`selfhosted:`)

#### XWiki (`selfhosted.xwiki`)

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `base_url` | string | — | XWiki instance URL (e.g. `https://wiki.acme.example`) |
| `space_key` | string | `QMS` | XWiki space key for the QMS |
| `username` | string | — | XWiki admin username |
| `parent_page` | string | `QM Manual` | Parent page for all QMS pages |

Password via env var: `XWIKI_PASSWORD`

#### Redmine (`selfhosted.redmine`)

| Key | Type | Description |
|-----|------|-------------|
| `base_url` | string | Redmine instance URL (e.g. `https://redmine.acme.example`) |
| `project_key` | string | Redmine project identifier (e.g. `qms-acme`) |
| `tracker_mapping` | map | Maps record type IDs to Redmine tracker names |

API key via env var: `REDMINE_API_KEY`

**Tracker mapping example:**

```yaml
tracker_mapping:
nc: "Nonconformity" # must match the tracker name in Redmine exactly
capa: "CAPA"
audit: "Internal Audit"
kpi: "KPI Measurement"
```

## Core Config Keys
> **API limitation (verified):** Redmine tracker and custom field *definitions* cannot be created
> via the API. Run the one-time manual setup first — see [redmine-setup.md](redmine-setup.md).

See [`config/core.yaml`](../config/core.yaml) — all keys are documented inline.
---

## Overlay Keys
## Environment variables

| Key | Description |
|-----|-------------|
| `target` | `selfhosted` / `atlassian` / `m365` |
| `organisation.name` | Full legal name of the client organisation |
| `organisation.short` | Short identifier used in document IDs |
| `organisation.quality_officer` | Full name of the QMO |
| `organisation.management` | Full name of the approving manager |
Secrets are never stored in config files. Pass them as environment variables before running `qms-kit deploy`.

### Self-hosted specific
| Variable | Required for | Description |
|----------|-------------|-------------|
| `XWIKI_PASSWORD` | Self-hosted | XWiki admin password |
| `REDMINE_API_KEY` | Self-hosted | Redmine API key (from My Account → API access key) |
| `CONFLUENCE_API_TOKEN` | Atlassian (Phase 2) | Confluence Cloud API token |
| `JIRA_API_TOKEN` | Atlassian (Phase 2) | Jira Cloud API token |
| `SHAREPOINT_CLIENT_SECRET` | M365 (Phase 3) | SharePoint Graph API client secret |

| Key | Description |
|-----|-------------|
| `xwiki.base_url` | XWiki instance URL |
| `xwiki.space_key` | XWiki space key for the QMS (default: `QMS`) |
| `redmine.base_url` | Redmine instance URL |
| `redmine.project_key` | Redmine project identifier |
| `redmine.tracker_mapping` | Maps record type IDs to Redmine tracker names |
---

## Environment Variables
## Validation

Secrets are never stored in config files — pass them as environment variables:
The config is validated with Pydantic at load time. Common errors:

| Variable | Used by |
|----------|---------|
| `XWIKI_PASSWORD` | XWiki REST authentication |
| `REDMINE_API_KEY` | Redmine REST authentication |
| `CONFLUENCE_API_TOKEN` | Confluence REST authentication (Phase 2) |
| `JIRA_API_TOKEN` | Jira REST authentication (Phase 2) |
| `SHAREPOINT_CLIENT_SECRET` | SharePoint Graph API (Phase 3) |
- **Duplicate document IDs** → `ValueError: Duplicate document IDs: {...}`
- **Missing required field** → `ValidationError: Field required`
- **Missing selfhosted block** → `ValueError: selfhosted config block is required`
- **Unknown template variable** → `jinja2.UndefinedError: ... is undefined`
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src", "cli", "adapters"]

[tool.uv]
package = true

[project]
name = "qms-kit"
version = "0.1.0"
Expand All @@ -17,6 +27,7 @@ dev = [
"pytest-cov>=5.0",
"mypy>=1.10",
"bandit>=1.8",
"hatchling>=1.25",
"types-pyyaml>=6.0",
"types-requests>=2.31",
]
Expand Down
59 changes: 34 additions & 25 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ Fill in your organisation's content over the following two weeks.

**qms-kit deploys the structure. You fill in the content.**

One command wires up spaces, document templates, issue trackers, approval workflows, and KPI
dashboards for ISO 9001 clauses 4–10 — in XWiki + Redmine (self-hosted), Confluence + Jira
(Atlassian), or SharePoint + Power Automate (M365).
Reuse the same kit for every client; swap the overlay config.
One command wires up spaces, document templates, issue trackers, and KPI dashboards for
ISO 9001 clauses 4–10 — in XWiki + Redmine (self-hosted), Confluence + Jira (Atlassian),
or SharePoint + Power Automate (M365). Reuse the same kit for every client; swap the overlay config.

> ⚠️ **Scope:** qms-kit delivers a documentation scaffold for internal maturity and
> customer/second-party audits. Accredited certification requires an additional operating phase
Expand All @@ -41,42 +40,53 @@ Reuse the same kit for every client; swap the overlay config.
- 📋 **ISO 9001 clauses 4–10 as structure** — every space, page, and tracker maps to a clause.
- ⚙️ **Config-driven, idempotent** — declare your QMS in YAML; run deploy as often as needed.
- 🔁 **Multi-tenant** — one core config, one overlay per client. No copy-paste between engagements.
- 🧩 **Adapter model** — self-hosted, Atlassian, and M365 are interchangeable modules; the core never changes.
- 📄 **Jinja2 templates** — policy, procedure, management review agenda, audit checklist, CAPA form — all with placeholders, ready to hand to your client.
- 🧩 **Adapter model** — self-hosted, Atlassian, and M365 are interchangeable modules.
- 📄 **Jinja2 templates** — quality policy, procedures, management review agenda, internal audit checklist, CAPA form — all with placeholders, ready to hand to your client.

---

## 📦 What gets deployed

| Layer | What |
|-------|------|
| **Structure** | Space / site / project, document tree, naming conventions |
| **Structure** | Space / project, document tree, naming conventions |
| **Document templates** | Quality policy, procedures, MR agenda, internal audit checklist, CAPA/NC form |
| **Record containers** | Issue schemas for NC, CAPA, audits, objectives/KPIs |
| **Workflows** | Approval/review routing, CAPA escalation, review reminders |
| **Dashboards** | KPI and status overview |
| **Record containers** | Issue trackers for NC, CAPA, audits, KPIs |

---

## 🚀 Quickstart

> Self-hosted target (XWiki + Redmine via Docker Compose):
Self-hosted target (XWiki + Redmine via Docker Compose):

```bash
# 1. Copy and fill in your client overlay
cp config/selfhosted.yaml config/clients/acme.yaml
# edit acme.yaml: org name, XWiki URL, Redmine URL, credentials
# 1. Clone and install
git clone https://github.com/gerfru/qms-kit.git
cd qms-kit
uv sync

# 2. Start the stack (first time only — brings up XWiki + Redmine)
docker compose -f adapters/selfhosted/docker-compose.yml up -d
# 2. Start XWiki + Redmine
cp docker/.env.example docker/.env # fill in passwords
docker compose -f docker/docker-compose.yml up -d

# 3. Seed the QMS scaffold
python -m cli deploy --target selfhosted --config config/clients/acme.yaml
# 3. One-time Redmine setup (manual — API limitation)
# See docs/redmine-setup.md

# 4. Create your client overlay
cp config/clients/example.yaml config/clients/acme.yaml
# edit acme.yaml: org name, URLs, credentials

# 5. Dry-run first (no changes written)
export XWIKI_PASSWORD=Admin
export REDMINE_API_KEY=<your-key>
qms-kit deploy --target selfhosted --config config/clients/acme.yaml --dry-run

# 6. Deploy
qms-kit deploy --target selfhosted --config config/clients/acme.yaml
```

> **Note on Redmine custom fields:** Field *definitions* (Root Cause, Effectiveness Check, Status)
> must be created once manually or via plugin — the Redmine API does not support creating them.
> The deployer seeds projects, issues, and field *values* automatically.
> **Redmine custom fields** (Root Cause, Effectiveness Check, etc.) must be created once
> manually — the Redmine API does not support creating field definitions.
> See [docs/redmine-setup.md](docs/redmine-setup.md).

→ Full walkthrough: **[docs/setup.md](docs/setup.md)**
Expand All @@ -87,7 +97,7 @@ python -m cli deploy --target selfhosted --config config/clients/acme.yaml

| Target | Status | Stack |
|--------|--------|-------|
| **Self-hosted** | ✅ Phase 1 | XWiki (docs) + Redmine (records) via Docker |
| **Self-hosted** | ✅ Phase 1 complete | XWiki (docs) + Redmine (records) via Docker |
| **Atlassian** | 🔜 Phase 2 | Confluence + Jira via REST API |
| **Microsoft 365** | 🔜 Phase 3 | SharePoint + Power Automate via PnP PowerShell |

Expand All @@ -111,11 +121,10 @@ python -m cli deploy --target selfhosted --config config/clients/acme.yaml

## 🧱 Stack

Python · Jinja2 · YAML · Docker Compose · XWiki REST API · Redmine REST API
Python · Jinja2 · Pydantic · YAML · Click · Docker Compose · XWiki REST API · Redmine REST API

Three layers above the adapters: a config loader with Pydantic schema validation, a Jinja2
template renderer, and a single CLI entry point (`deploy --target`).
M365 adapter shells out to PnP PowerShell; everything else is pure Python.
template renderer, and a single CLI entry point (`qms-kit deploy --target`).

---

Expand Down
Loading
Loading