Skip to content
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,9 @@ dmypy.json
# Pyre type checker
.pyre/
.idea

trial-runs
manual-tests/work/*
!manual-tests/work/.gitkeep
manual-tests/artifacts/*
!manual-tests/artifacts/.gitkeep
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Before proposing or running end-to-end OpenPlate commands, read [manual-tests/index.md](manual-tests/index.md) and the relevant numbered case document under [manual-tests](manual-tests).

Use the checked-in commands, fixtures, and validation checklists in [manual-tests/case-1.md](manual-tests/case-1.md), [manual-tests/case-2.md](manual-tests/case-2.md), [manual-tests/case-3.md](manual-tests/case-3.md), and [manual-tests/case-4.md](manual-tests/case-4.md) instead of reconstructing workflows from memory.
111 changes: 111 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ Some templates take advantage of a "sub-folder" to init into. This allows the t
openplate init --dest-folder=src git@github.com:my-org/ot-docker.git#v1
```

### Re-running Init

- A plain rerun of `openplate init` for the same tracked template and dest-folder is rejected.
- `openplate init --overwrite` reruns init for the same tracked template and dest-folder and overwrites tracked template output.
- `openplate init --overwrite` skips init-command reruns and reuses the existing tracked template entry in `.openplate.project.yaml`.

## Command: update

Update the current project with the latest versions of the template
Expand All @@ -77,6 +83,109 @@ openplate update

The legacy nested `project` variant still works for compatibility, but `openplate update` is the documented command.

Common update modes:

```
openplate update --update-missing
openplate update --update-full
```

- `--update-missing` recreates missing non-readonly files without overwriting existing non-readonly files.
- `--update-full` is the overwrite-oriented maintenance mode. It recreates missing non-readonly files and overwrites existing non-readonly files.

## Prompt JSON Workflow

For machine-driven init runs, OpenPlate can export the prompt state as JSON, let you fill in only the answers you care about, and then consume that JSON during `init` without falling back to interactive prompting.

Export the init prompt tree:

```
openplate project print-init-json https://github.com/my-org/ot-template.git#v1
openplate project print-init-json https://github.com/my-org/ot-template.git#v1 --verbose
```

Import answers from a file or standard input:

```
openplate init https://github.com/my-org/ot-template.git#v1 --prompts-json-file prompts.json
type prompts.json | openplate init https://github.com/my-org/ot-template.git#v1 --prompts-json-stdin
```

`project print-init-json` is read-only. It does not update `.openplate.project.yaml` or write template output.

The compact export format is a top-level JSON array of prompt nodes:

```json
[
{
"node-id": "15cff52",
"answers": {
"service_name": null
}
}
]
```

The verbose export includes the same `node-id` and `answers` fields plus `info` metadata:

```json
[
{
"node-id": "15cff52",
"answers": {
"service_name": null
},
"info": {
"template": "https://github.com/my-org/ot-template.git#v1",
"dest_folder": ".",
"parameters": {
"service_name": {
"default": null,
"existing": null,
"description": "Service Name",
"choices": null,
"hidden": null,
"required": true
}
}
}
}
]
```

Key semantics:

- `node-id` is the import/export identity for a reached init node.
- `answers` contains only the prompt answers used on import.
- compact export omits `info`.
- verbose export includes `info.template`, init-relative `info.dest_folder`, and prompt metadata.
- when present, verbose `info.require_sibling_templates` describes caller-side sibling declarations, including any sibling `condition` metadata.
- the init root from `openplate init --dest-folder ...` is not part of exported `node-id` values or exported `info.dest_folder` values.

Hidden parameters are included only when the command uses `--ask-hidden`. Without `--ask-hidden`, hidden parameters are omitted from prompt JSON export and ignored on prompt JSON import.

Answer semantics:

- `null` means do not answer this parameter from JSON; if the parameter is reached, OpenPlate uses the normal runtime fallback such as an existing value or template/default value
- `""` means an intentional blank string answer
- any other non-null string means an explicit supplied answer for that parameter
- omitting an answer key also means unresolved, so normal runtime fallback applies if that parameter is reached

Import semantics:

- OpenPlate matches imported prompt JSON by `node-id`.
- `info` is ignored on import.
- For parameters in scope for the command, any non-null answer is authoritative even if runtime fallback already has an existing or default value.
- `--ask-again` affects interactive prompting. It does not prevent a non-null prompt JSON answer from being applied.

Notes:

- `project print-init-json` is the only mode that walks the full declared sibling tree without applying sibling `condition` filters.
- `--prompts-json-file` and `--prompts-json-stdin` are supported for init only.
- `project update` does not expose prompt JSON flags.
- imported nodes that are not processed by the run are ignored and logged by `node-id`.
- OpenPlate warns when supplied prompt answers are left unused for a matched node.

## Command: project verify

Verify that the project has not drifted from the template. Exit with code -1 if so.
Expand Down Expand Up @@ -107,6 +216,8 @@ or
openplate update --ask-hidden
```

The same flag controls prompt JSON scope. With `--ask-hidden`, hidden parameters are included in `project print-init-json` output and may be answered through `--prompts-json-file` or `--prompts-json-stdin` on `init`. Without it, hidden parameters are omitted from export and ignored on import.

# Template Branches

NOTE: to use a specific branch or tag of a template, append `#branchname` on init. If you omit `#branchname`, you must also pass `--allow-default-branch`.
Expand Down
4 changes: 3 additions & 1 deletion docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ parameters:
hidden: True
```

In order for a user to specify it, they need to pass "--ask-hidden" to their command and possiblly "--ask-again"
In order for a user to specify it interactively, they need to pass "--ask-hidden" to their command and possibly "--ask-again".

The same `--ask-hidden` flag also controls prompt JSON scope. Hidden parameters are included in `openplate project print-init-json` output only when `--ask-hidden` is used, and hidden values from `openplate init --prompts-json-file` or `openplate init --prompts-json-stdin` are applied only when `--ask-hidden` is active for that command.

### "Conditionally Hidden"

Expand Down
70 changes: 70 additions & 0 deletions manual-tests/case-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Case 1: Config Persistence and URL-Source Init

## Purpose / Covered Commands

- `openplate --version`
- `openplate config set`
- `openplate config get`
- `openplate init` with `file://` sources, `?path=`, `#main`, `--allow-default-branch`, `--dest-folder`, `--no-cache`, `--ignore`, rerun rejection, `--overwrite`, and `--ignore-tool-version`

Matrix facets covered by this case:

- `global --version`
- `global --config-file`
- `global --project-folder`
- `global --ignore-tool-version`
- `config get`
- `config set --vcs-url`
- `config set --template-prefix`
- `config set --parameter-default add/remove`
- `config set --allow-template-commands`
- `init positional source URL`
- `init file:// transport`
- `init ?path= sub-folder selection`
- `init explicit #ref`
- `init --allow-default-branch`
- `init --dest-folder`
- `init --no-cache`
- `init --ignore`
- `init rerun rejection without --overwrite`
- `init --overwrite`

## Prerequisites

- Run from the repository root in `bash`.
- Ensure `bash`, `python`, and `git` are on `PATH`.
- Do not replace the checked-in fixture catalog with external or private templates.

## Exact Commands To Run

```bash
bash ./manual-tests/cleanup-manual-tests.sh case-1
bash ./manual-tests/run-manual-tests.sh case-1
```

## Expected Scripted Outputs

- [manual-tests/artifacts/case-1/01-version.log](manual-tests/artifacts/case-1/01-version.log) records the CLI version output.
- [manual-tests/artifacts/case-1/04-config-get.log](manual-tests/artifacts/case-1/04-config-get.log) shows persisted `vcs_url`, `template_prefix`, and the retained `service_name` default while omitting the removed `owner_name` default.
- [manual-tests/artifacts/case-1/summary.txt](manual-tests/artifacts/case-1/summary.txt) lists the materialized local catalog repo and the source URLs used.
- [manual-tests/work/case-1/bootstrap-project/.openplate.project.yaml](manual-tests/work/case-1/bootstrap-project/.openplate.project.yaml) contains `dest_folder: bootstrap/app` and `no_cache: true`.
- [manual-tests/work/case-1/bootstrap-project/scaffold/bootstrap/app/managed/service.txt](manual-tests/work/case-1/bootstrap-project/scaffold/bootstrap/app/managed/service.txt) contains the config-default service name and defaulted lifecycle values.
- [manual-tests/artifacts/case-1/06-init-rerun-rejected.log](manual-tests/artifacts/case-1/06-init-rerun-rejected.log) records the rejected second plain `init` run against the same tracked template and dest-folder.
- [manual-tests/artifacts/case-1/07-init-overwrite.log](manual-tests/artifacts/case-1/07-init-overwrite.log) records the accepted `init --overwrite` rerun against the same tracked template.
- [manual-tests/work/case-1/default-branch-project/scaffold/branchless/app/managed/service.txt](manual-tests/work/case-1/default-branch-project/scaffold/branchless/app/managed/service.txt) proves the branchless `file://` source worked with `--allow-default-branch`.
- [manual-tests/work/case-1/default-branch-project/scaffold/branchless/app/notes/runbook.md](manual-tests/work/case-1/default-branch-project/scaffold/branchless/app/notes/runbook.md) is intentionally absent because `--ignore` filtered it.
- [manual-tests/work/case-1/version-gated-project/gated/info.txt](manual-tests/work/case-1/version-gated-project/gated/info.txt) proves the version-gated fixture can be initialized only when `--ignore-tool-version` is supplied.

## Manual Validation Checklist

- Confirm the recorded source URLs in [manual-tests/artifacts/case-1/summary.txt](manual-tests/artifacts/case-1/summary.txt) are local `file://` URLs only.
- Confirm the bootstrap project created [manual-tests/work/case-1/bootstrap-project/hooks/init-command.txt](manual-tests/work/case-1/bootstrap-project/hooks/init-command.txt), showing the persisted `config set --allow-template-commands` setting took effect.
- Confirm the plain rerun log rejects the second `init` attempt for the same dest-folder and leaves the removed bootstrap files absent.
- Confirm the overwrite rerun restored the removed bootstrap files but did not recreate [manual-tests/work/case-1/bootstrap-project/hooks/init-command.txt](manual-tests/work/case-1/bootstrap-project/hooks/init-command.txt), demonstrating that `init --overwrite` skips init-command reruns.
- Confirm [manual-tests/work/case-1/bootstrap-project/.openplate.project.yaml](manual-tests/work/case-1/bootstrap-project/.openplate.project.yaml) still contains only one tracked template entry after the overwrite rerun.
- Confirm the default-branch project has no generated runbook file, and that the rest of the scaffold exists under the requested `bootstrap/app` or `branchless/app` dest-folder segments.
- Confirm no case output references network URLs, private repositories, or company-specific fixture names.

## Cleanup Notes

- The shared cleanup script removes only [manual-tests/work/case-1](manual-tests/work/case-1) and [manual-tests/artifacts/case-1](manual-tests/artifacts/case-1).
48 changes: 48 additions & 0 deletions manual-tests/case-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Case 2: Interactive Init, Hidden Parameters, and Template Commands

## Purpose / Covered Commands

- `openplate init` interactive prompting
- Template-command authorization behavior
- Hidden parameter defaults and `--ask-hidden`
- `conditionally_hidden` behavior driven by earlier answers

Matrix facets covered by this case:

- `global --config-file`
- `global --project-folder`
- `global --ask-hidden`
- `config set --allow-template-commands`
- `init interactive prompting`
- `init template-command authorization guidance`
- `init hidden parameters`
- `init conditionally_hidden`

## Prerequisites

- Run from the repository root in `bash`.
- Ensure `bash`, `python`, and `git` are on `PATH`.

## Exact Commands To Run

```bash
bash ./manual-tests/cleanup-manual-tests.sh case-2
bash ./manual-tests/run-manual-tests.sh case-2
```

## Expected Scripted Outputs

- [manual-tests/artifacts/case-2/01-blocked-init-commands.log](manual-tests/artifacts/case-2/01-blocked-init-commands.log) contains the exact non-interactive safety guidance for template `init_commands` and exits with failure.
- [manual-tests/artifacts/case-2/02-config-allow-init-commands.log](manual-tests/artifacts/case-2/02-config-allow-init-commands.log) records the config toggle that permanently allows template commands for this case config file.
- [manual-tests/work/case-2/default-visibility-project/scaffold/interactive/default/managed/service.txt](manual-tests/work/case-2/default-visibility-project/scaffold/interactive/default/managed/service.txt) contains `deployment=lambda`, `instance=t3.small`, and `hidden=hidden-default`, proving hidden and conditionally hidden values fell back without prompting.
- [manual-tests/work/case-2/ask-hidden-project/scaffold/interactive/hidden/managed/service.txt](manual-tests/work/case-2/ask-hidden-project/scaffold/interactive/hidden/managed/service.txt) contains `deployment=ec2`, `instance=m5.large`, and `hidden=override-secret`, proving `--ask-hidden` exposed both the hidden parameter and the `conditionally_hidden` parameter once its condition evaluated to visible.

## Manual Validation Checklist

- Confirm the blocked init log recommends both the one-time `openplate init --allow-template-commands` override and the persistent `openplate config set --allow-template-commands` setting.
- Confirm both successful projects created [manual-tests/work/case-2/default-visibility-project/hooks/init-command.txt](manual-tests/work/case-2/default-visibility-project/hooks/init-command.txt) and [manual-tests/work/case-2/ask-hidden-project/hooks/init-command.txt](manual-tests/work/case-2/ask-hidden-project/hooks/init-command.txt).
- Confirm the default-visibility run never required an explicit `instance_type` or `hidden_token` answer, while the `--ask-hidden` run materialized the explicit values.

## Cleanup Notes

- The shared cleanup script removes only [manual-tests/work/case-2](manual-tests/work/case-2) and [manual-tests/artifacts/case-2](manual-tests/artifacts/case-2).
58 changes: 58 additions & 0 deletions manual-tests/case-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Case 3: Prompt JSON Export and Import

## Purpose / Covered Commands

- `openplate project print-init-json`
- `openplate project print-init-json --verbose`
- `openplate init --prompts-json-file`
- `openplate init --prompts-json-stdin`

Matrix facets covered by this case:

- `global --config-file`
- `global --project-folder`
- `global --debug`
- `global --ask-hidden`
- `prompt JSON compact export`
- `prompt JSON verbose export`
- `prompt JSON file import`
- `prompt JSON stdin import`
- `prompt JSON hidden-scope behavior`
- `prompt JSON dest-folder-independent node identity`
- `prompt JSON ignored extra node behavior`
- `prompt JSON ignored extra answer behavior`
- `project print-init-json read-only behavior`

## Prerequisites

- Run from the repository root in `bash`.
- Ensure `bash`, `python`, and `git` are on `PATH`.

## Exact Commands To Run

```bash
bash ./manual-tests/cleanup-manual-tests.sh case-3
bash ./manual-tests/run-manual-tests.sh case-3
```

## Expected Scripted Outputs

- [manual-tests/artifacts/case-3/prompts-compact.json](manual-tests/artifacts/case-3/prompts-compact.json) is a JSON array that omits `info` metadata.
- [manual-tests/artifacts/case-3/prompts-verbose.json](manual-tests/artifacts/case-3/prompts-verbose.json) includes `info`, hidden parameter metadata, and sibling declaration metadata.
- [manual-tests/work/case-3/export-workspace/.openplate.project.yaml](manual-tests/work/case-3/export-workspace/.openplate.project.yaml) does not exist after either export command.
- [manual-tests/artifacts/case-3/03-init-prompts-json-file.log](manual-tests/artifacts/case-3/03-init-prompts-json-file.log) contains both ignored-node and ignored-extra-answer warnings because the import intentionally includes one unmatched node and one unused parameter.
- [manual-tests/work/case-3/file-import-project/generated/imported/root/managed/root.txt](manual-tests/work/case-3/file-import-project/generated/imported/root/managed/root.txt) contains `service=json-file-service` and `hidden=json-hidden`, proving the file import used prompt answers and that the node matched even with a different init `--dest-folder`.
- [manual-tests/work/case-3/file-import-project/generated/worker/json-file-service/managed/worker.txt](manual-tests/work/case-3/file-import-project/generated/worker/json-file-service/managed/worker.txt) exists because the imported prompt data kept the sibling condition enabled.
- [manual-tests/work/case-3/stdin-import-project/generated/stdin/root/managed/root.txt](manual-tests/work/case-3/stdin-import-project/generated/stdin/root/managed/root.txt) contains `service=json-stdin-service`.
- [manual-tests/work/case-3/stdin-import-project/generated/worker/json-stdin-service/managed/worker.txt](manual-tests/work/case-3/stdin-import-project/generated/worker/json-stdin-service/managed/worker.txt) is intentionally absent because the stdin import disabled the sibling branch.

## Manual Validation Checklist

- Open the compact and verbose artifacts side by side and confirm only the verbose export carries `info` metadata.
- Confirm the export workspace remained read-only from OpenPlate’s perspective by checking that [manual-tests/work/case-3/export-workspace](manual-tests/work/case-3/export-workspace) has no project config or generated template files.
- Confirm the file-import log includes the warning text for both ignored unmatched nodes and ignored unused prompt parameters.
- Confirm all recorded source URLs inside the JSON artifacts still point at the local materialized catalog repo for this case.

## Cleanup Notes

- The shared cleanup script removes only [manual-tests/work/case-3](manual-tests/work/case-3) and [manual-tests/artifacts/case-3](manual-tests/artifacts/case-3).
Loading