Skip to content

Commit 698d5e1

Browse files
committed
Merge branch '0.6'
2 parents f1f0cc1 + 97d6c9a commit 698d5e1

File tree

9 files changed

+239
-42
lines changed

9 files changed

+239
-42
lines changed

.claude/commands/code-review.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,29 @@
11
---
2-
allowed-tools: Bash(git diff:*), Bash(git log:*), Bash(git merge-base:*), Bash(git rev-parse:*)
2+
allowed-tools: Bash(git branch:*), Bash(git diff:*), Bash(git log:*), Bash(git merge-base:*), Bash(git rev-parse:*)
33
description: Code review changes since branch diverged from default branch
44
---
55

6-
Review the code changes introduced since this branch diverged from the default branch (main).
6+
Review the code changes introduced since this branch diverged from its base branch.
77

88
**Agent assumptions (applies to all agents and subagents):**
99
- All tools are functional and will work without error. Do not test tools or make exploratory calls. Make sure this is clear to every subagent that is launched.
1010
- Only call a tool if it is required to complete the task. Every tool call should have a clear purpose.
1111

1212
To do this, follow these steps precisely:
1313

14-
1. Run `git diff main...HEAD` to get the diff and `git log main..HEAD --oneline` to get the commit list. If the diff is empty, stop and report there are no changes to review.
14+
1. Determine the base branch to diff against:
15+
- The default base branch is `main`.
16+
- Run `git branch` to list local branches and find any version branches (branches whose name matches the pattern `X.Y`, e.g. `0.6`, `1.2`).
17+
- For each version branch found, compare how close the current branch is to it vs. `main` by counting commits since divergence: `git log <branch>..HEAD --oneline | wc -l`. Pick the branch with the fewest commits (i.e. the most recent common ancestor). If a version branch is closer than `main`, use it as the base branch instead.
18+
- Store the chosen base branch name as BASE_BRANCH.
1519

16-
2. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including:
20+
2. Run `git diff BASE_BRANCH...HEAD` to get the diff and `git log BASE_BRANCH..HEAD --oneline` to get the commit list. If the diff is empty, stop and report there are no changes to review.
21+
22+
3. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including:
1723
- The root CLAUDE.md file, if it exists
1824
- Any CLAUDE.md files in directories containing files modified in the diff
1925

20-
3. Launch 4 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. "CLAUDE.md adherence", "bug"). The agents should do the following:
26+
4. Launch 4 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. "CLAUDE.md adherence", "bug"). The agents should do the following:
2127

2228
Agents 1 + 2: CLAUDE.md compliance sonnet agents
2329
Audit changes for CLAUDE.md compliance in parallel. Note: When evaluating CLAUDE.md compliance for a file, you should only consider CLAUDE.md files that share a file path with the file or parents.
@@ -40,11 +46,11 @@ To do this, follow these steps precisely:
4046

4147
If you are not certain an issue is real, do not flag it. False positives erode trust and waste reviewer time.
4248

43-
4. For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as "variable is not defined" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.
49+
5. For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as "variable is not defined" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.
4450

45-
5. Filter out any issues that were not validated in step 4. This step will give us our list of high signal issues for our review.
51+
6. Filter out any issues that were not validated in step 4. This step will give us our list of high signal issues for our review.
4652

47-
6. Output a summary of the review findings to the terminal:
53+
7. Output a summary of the review findings to the terminal:
4854
- If issues were found, list each issue with a brief description and the file/line it occurs in.
4955
- If no issues were found, state: "No issues found. Checked for bugs and CLAUDE.md compliance."
5056

.claude/settings.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(symfony composer run *)"
5+
],
6+
"deny": [
7+
"Read(.env)",
8+
"Read(.env.dirigent.local)",
9+
"Read(.env.dirigent.local.php)",
10+
"Read(.env.dirigent.*.local)",
11+
"Read(./config/secrets/prod/prod.decrypt.private.php)"
12+
]
13+
}
14+
}

AGENTS.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ tests/
4848
- Always use strict comparisons (`===`, `!==`).
4949
- Enforce the use of DateTimeImmutable over DateTime.
5050
- Always use spaces in concatenation (`$a . $b`).
51-
- Always use imports. Use aliases when collisions occur or the imported name is unclear.
51+
- Always use imports. Use aliases when collisions occur or the imported name is unclear. Do not import classes from the root namespace (e.g. `\RuntimeException`, `\Stringable`); use the fully-qualified backslash prefix inline instead.
5252
- Don't use blank lines between import groups.
5353

5454
## Commands
@@ -57,30 +57,30 @@ tests/
5757

5858
```shell
5959
# Run all linting jobs
60-
symfony composer lint
60+
symfony composer run lint
6161

6262
# Individual linters
63-
symfony composer lint:refactor # Rector (automatically applies changes)
64-
symfony composer lint:coding-style # PHP-CS-Fixer (automatically applies changes)
65-
symfony composer lint:static-analysis # PHPStan level 5
66-
symfony composer lint:container # Symfony container validation
67-
symfony composer lint:templates # Twig template validation
63+
symfony composer run lint:refactor # Rector (automatically applies changes)
64+
symfony composer run lint:coding-style # PHP-CS-Fixer (automatically applies changes)
65+
symfony composer run lint:static-analysis # PHPStan level 5
66+
symfony composer run lint:container # Symfony container validation
67+
symfony composer run lint:templates # Twig template validation
6868
```
6969

7070
### Testing
7171

7272
```shell
7373
# Prepare the Symfony test environment for tests (if the database schema changed)
74-
symfony composer tests:setup
74+
symfony composer run tests:setup
7575

7676
# Run all tests
77-
symfony composer tests
77+
symfony composer run tests
7878

7979
# Run only PHP tests
80-
symfony composer tests:php
81-
symfony composer tests:php:unit
82-
symfony composer tests:php:functional
80+
symfony composer run tests:php
81+
symfony composer run tests:php:unit
82+
symfony composer run tests:php:functional
8383

8484
# Run tests for Docker images
85-
symfony composer tests:docker
85+
symfony composer run tests:docker
8686
```

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
* 0.6.2 (2026-03-31)
4+
* Improved sorting of package keywords by adding an index to the stored metadata
5+
36
* 0.6.1 (2026-03-30)
47
* Fixed loading of encryption module in dashboard pages which prevented changes to existing credentials
58
* Disabled autocomplete on credentials information
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
final class Version20260323085127 extends AbstractMigration
11+
{
12+
public function getDescription(): string
13+
{
14+
return 'Add index to version keyword association';
15+
}
16+
17+
public function up(Schema $schema): void
18+
{
19+
$this->addSql(<<<'SQL'
20+
ALTER TABLE version_keyword DROP CONSTRAINT fk_a65a946f115d4552
21+
SQL);
22+
$this->addSql(<<<'SQL'
23+
ALTER TABLE version_keyword DROP CONSTRAINT fk_a65a946f4bbc2705
24+
SQL);
25+
$this->addSql(<<<'SQL'
26+
ALTER TABLE version_keyword DROP CONSTRAINT version_keyword_pkey
27+
SQL);
28+
$this->addSql(<<<'SQL'
29+
ALTER TABLE version_keyword ADD id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL
30+
SQL);
31+
$this->addSql(<<<'SQL'
32+
ALTER TABLE version_keyword ADD index INT DEFAULT NULL
33+
SQL);
34+
$this->addSql(<<<'SQL'
35+
UPDATE version_keyword
36+
SET index = sub.row_num - 1
37+
FROM (
38+
SELECT version_id, keyword_id, ROW_NUMBER() OVER (PARTITION BY version_id ORDER BY keyword_id) AS row_num
39+
FROM version_keyword
40+
) sub
41+
WHERE version_keyword.version_id = sub.version_id AND version_keyword.keyword_id = sub.keyword_id
42+
SQL);
43+
$this->addSql(<<<'SQL'
44+
ALTER TABLE version_keyword ALTER COLUMN index SET NOT NULL
45+
SQL);
46+
$this->addSql(<<<'SQL'
47+
ALTER TABLE
48+
version_keyword
49+
ADD
50+
CONSTRAINT FK_A65A946F115D4552 FOREIGN KEY (keyword_id) REFERENCES keyword (id) NOT DEFERRABLE
51+
SQL);
52+
$this->addSql(<<<'SQL'
53+
ALTER TABLE
54+
version_keyword
55+
ADD
56+
CONSTRAINT FK_A65A946F4BBC2705 FOREIGN KEY (version_id) REFERENCES version (id) NOT DEFERRABLE
57+
SQL);
58+
$this->addSql(<<<'SQL'
59+
ALTER TABLE version_keyword ADD PRIMARY KEY (id)
60+
SQL);
61+
}
62+
63+
public function down(Schema $schema): void
64+
{
65+
$this->addSql(<<<'SQL'
66+
ALTER TABLE version_keyword DROP CONSTRAINT FK_A65A946F4BBC2705
67+
SQL);
68+
$this->addSql(<<<'SQL'
69+
ALTER TABLE version_keyword DROP CONSTRAINT FK_A65A946F115D4552
70+
SQL);
71+
$this->addSql(<<<'SQL'
72+
ALTER TABLE version_keyword DROP CONSTRAINT version_keyword_pkey
73+
SQL);
74+
$this->addSql(<<<'SQL'
75+
ALTER TABLE version_keyword DROP id
76+
SQL);
77+
$this->addSql(<<<'SQL'
78+
ALTER TABLE version_keyword DROP index
79+
SQL);
80+
$this->addSql(<<<'SQL'
81+
ALTER TABLE
82+
version_keyword
83+
ADD
84+
CONSTRAINT fk_a65a946f4bbc2705 FOREIGN KEY (version_id) REFERENCES version (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
85+
SQL);
86+
$this->addSql(<<<'SQL'
87+
ALTER TABLE
88+
version_keyword
89+
ADD
90+
CONSTRAINT fk_a65a946f115d4552 FOREIGN KEY (keyword_id) REFERENCES keyword (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
91+
SQL);
92+
$this->addSql(<<<'SQL'
93+
ALTER TABLE version_keyword ADD PRIMARY KEY (version_id, keyword_id)
94+
SQL);
95+
}
96+
}

src/Doctrine/Entity/Keyword.php

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
namespace CodedMonkey\Dirigent\Doctrine\Entity;
44

55
use CodedMonkey\Dirigent\Doctrine\Repository\KeywordRepository;
6-
use Doctrine\Common\Collections\ArrayCollection;
7-
use Doctrine\Common\Collections\Collection;
86
use Doctrine\ORM\Mapping as ORM;
97

108
#[ORM\Entity(repositoryClass: KeywordRepository::class)]
@@ -18,13 +16,9 @@ class Keyword
1816
#[ORM\Column(length: 191)]
1917
private string $name;
2018

21-
#[ORM\ManyToMany(targetEntity: Version::class, mappedBy: 'keywords')]
22-
protected Collection $versions;
23-
2419
public function __construct(string $name)
2520
{
2621
$this->name = $name;
27-
$this->versions = new ArrayCollection();
2822
}
2923

3024
public function getId(): ?int

src/Doctrine/Entity/Version.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ class Version extends TrackedEntity implements \Stringable
7979
#[ORM\OrderBy(['index' => 'ASC'])]
8080
private Collection $suggest;
8181

82-
#[ORM\ManyToMany(targetEntity: Keyword::class, inversedBy: 'versions', cascade: ['persist', 'detach', 'remove'])]
82+
#[ORM\OneToMany(mappedBy: 'version', targetEntity: VersionKeyword::class, cascade: ['persist', 'detach', 'remove'])]
83+
#[ORM\OrderBy(['index' => 'ASC'])]
8384
private Collection $keywords;
8485

8586
#[ORM\Column]
@@ -354,16 +355,16 @@ public function addSuggestLink(VersionSuggestLink $suggest): void
354355
}
355356

356357
/**
357-
* @return Collection<int, Keyword>
358+
* @return Collection<int, VersionKeyword>
358359
*/
359360
public function getKeywords(): Collection
360361
{
361362
return $this->keywords;
362363
}
363364

364-
public function addKeyword(Keyword $keyword): void
365+
public function addKeyword(VersionKeyword $versionKeyword): void
365366
{
366-
$this->keywords[] = $keyword;
367+
$this->keywords[] = $versionKeyword;
367368
}
368369

369370
public function getAutoload(): array
@@ -642,8 +643,8 @@ public function getBrowsableRepositoryUrl(): ?string
642643
public function toComposerArray(): array
643644
{
644645
$keywords = [];
645-
foreach ($this->getKeywords() as $keyword) {
646-
$keywords[] = $keyword->getName();
646+
foreach ($this->getKeywords() as $versionKeyword) {
647+
$keywords[] = $versionKeyword->getKeyword()->getName();
647648
}
648649

649650
$authors = $this->getAuthors();
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
namespace CodedMonkey\Dirigent\Doctrine\Entity;
4+
5+
use Doctrine\ORM\Mapping as ORM;
6+
7+
#[ORM\Entity]
8+
class VersionKeyword
9+
{
10+
#[ORM\Id]
11+
#[ORM\Column]
12+
#[ORM\GeneratedValue]
13+
private ?int $id = null;
14+
15+
#[ORM\ManyToOne(targetEntity: Version::class, inversedBy: 'keywords')]
16+
#[ORM\JoinColumn(nullable: false)]
17+
private Version $version;
18+
19+
#[ORM\ManyToOne(targetEntity: Keyword::class)]
20+
#[ORM\JoinColumn(nullable: false)]
21+
private Keyword $keyword;
22+
23+
#[ORM\Column]
24+
private int $index;
25+
26+
public function getId(): ?int
27+
{
28+
return $this->id;
29+
}
30+
31+
public function getVersion(): Version
32+
{
33+
return $this->version;
34+
}
35+
36+
public function setVersion(Version $version): void
37+
{
38+
$this->version = $version;
39+
}
40+
41+
public function getKeyword(): Keyword
42+
{
43+
return $this->keyword;
44+
}
45+
46+
public function setKeyword(Keyword $keyword): void
47+
{
48+
$this->keyword = $keyword;
49+
}
50+
51+
public function getIndex(): int
52+
{
53+
return $this->index;
54+
}
55+
56+
public function setIndex(int $index): void
57+
{
58+
$this->index = $index;
59+
}
60+
61+
public function getName(): string
62+
{
63+
return $this->keyword->getName();
64+
}
65+
}

0 commit comments

Comments
 (0)